refactor: switch to eslint-plugin-perfectionist
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 21 Aug 2024 19:41:24 +0000 (21:41 +0200)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 21 Aug 2024 19:41:24 +0000 (21:41 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
124 files changed:
.lintstagedrc.js
benchmarks/benchmarks-types.cjs
benchmarks/benchmarks-utils.cjs
benchmarks/benchmarks-utils.mjs
benchmarks/internal/bench.mjs
benchmarks/internal/cluster-worker.cjs
benchmarks/worker-selection/least.mjs
eslint.config.js
examples/javascript/dynamicExample.cjs
examples/javascript/fixedExample.cjs
examples/javascript/multiFunctionExample.cjs
examples/typescript/http-client-pool/src/main.ts
examples/typescript/http-client-pool/src/pool.ts
examples/typescript/http-client-pool/src/types.ts
examples/typescript/http-client-pool/src/worker.ts
examples/typescript/http-server-pool/express-cluster/rollup.config.ts
examples/typescript/http-server-pool/express-cluster/src/main.ts
examples/typescript/http-server-pool/express-cluster/src/types.ts
examples/typescript/http-server-pool/express-cluster/src/worker.ts
examples/typescript/http-server-pool/express-hybrid/rollup.config.ts
examples/typescript/http-server-pool/express-hybrid/src/express-worker.ts
examples/typescript/http-server-pool/express-hybrid/src/main.ts
examples/typescript/http-server-pool/express-hybrid/src/request-handler-worker.ts
examples/typescript/http-server-pool/express-hybrid/src/types.ts
examples/typescript/http-server-pool/express-worker_threads/src/main.ts
examples/typescript/http-server-pool/express-worker_threads/src/pool.ts
examples/typescript/http-server-pool/express-worker_threads/src/worker.ts
examples/typescript/http-server-pool/fastify-cluster/rollup.config.ts
examples/typescript/http-server-pool/fastify-cluster/src/main.ts
examples/typescript/http-server-pool/fastify-cluster/src/types.ts
examples/typescript/http-server-pool/fastify-cluster/src/worker.ts
examples/typescript/http-server-pool/fastify-hybrid/@types/fastify/index.d.ts
examples/typescript/http-server-pool/fastify-hybrid/rollup.config.ts
examples/typescript/http-server-pool/fastify-hybrid/src/fastify-poolifier.ts
examples/typescript/http-server-pool/fastify-hybrid/src/fastify-worker.ts
examples/typescript/http-server-pool/fastify-hybrid/src/main.ts
examples/typescript/http-server-pool/fastify-hybrid/src/request-handler-worker.ts
examples/typescript/http-server-pool/fastify-hybrid/src/types.ts
examples/typescript/http-server-pool/fastify-worker_threads/@types/fastify/index.d.ts
examples/typescript/http-server-pool/fastify-worker_threads/src/fastify-poolifier.ts
examples/typescript/http-server-pool/fastify-worker_threads/src/main.ts
examples/typescript/http-server-pool/fastify-worker_threads/src/types.ts
examples/typescript/http-server-pool/fastify-worker_threads/src/worker.ts
examples/typescript/pool.ts
examples/typescript/smtp-client-pool/src/main.ts
examples/typescript/smtp-client-pool/src/pool.ts
examples/typescript/smtp-client-pool/src/types.ts
examples/typescript/smtp-client-pool/src/worker.ts
examples/typescript/websocket-server-pool/ws-cluster/requests.js
examples/typescript/websocket-server-pool/ws-cluster/rollup.config.ts
examples/typescript/websocket-server-pool/ws-cluster/src/main.ts
examples/typescript/websocket-server-pool/ws-cluster/src/types.ts
examples/typescript/websocket-server-pool/ws-cluster/src/worker.ts
examples/typescript/websocket-server-pool/ws-hybrid/requests.js
examples/typescript/websocket-server-pool/ws-hybrid/rollup.config.ts
examples/typescript/websocket-server-pool/ws-hybrid/src/main.ts
examples/typescript/websocket-server-pool/ws-hybrid/src/request-handler-worker.ts
examples/typescript/websocket-server-pool/ws-hybrid/src/types.ts
examples/typescript/websocket-server-pool/ws-hybrid/src/websocket-server-worker.ts
examples/typescript/websocket-server-pool/ws-worker_threads/requests.js
examples/typescript/websocket-server-pool/ws-worker_threads/src/main.ts
examples/typescript/websocket-server-pool/ws-worker_threads/src/pool.ts
examples/typescript/websocket-server-pool/ws-worker_threads/src/types.ts
examples/typescript/websocket-server-pool/ws-worker_threads/src/worker.ts
examples/typescript/worker.ts
package.json
pnpm-lock.yaml
rollup.config.mjs
src/circular-buffer.ts
src/pools/abstract-pool.ts
src/pools/cluster/dynamic.ts
src/pools/cluster/fixed.ts
src/pools/pool.ts
src/pools/selection-strategies/abstract-worker-choice-strategy.ts
src/pools/selection-strategies/fair-share-worker-choice-strategy.ts
src/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.ts
src/pools/selection-strategies/least-busy-worker-choice-strategy.ts
src/pools/selection-strategies/least-elu-worker-choice-strategy.ts
src/pools/selection-strategies/least-used-worker-choice-strategy.ts
src/pools/selection-strategies/round-robin-worker-choice-strategy.ts
src/pools/selection-strategies/selection-strategies-types.ts
src/pools/selection-strategies/selection-strategies-utils.ts
src/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.ts
src/pools/selection-strategies/worker-choice-strategies-context.ts
src/pools/thread/dynamic.ts
src/pools/thread/fixed.ts
src/pools/utils.ts
src/pools/worker-node.ts
src/pools/worker.ts
src/queues/abstract-fixed-queue.ts
src/queues/fixed-priority-queue.ts
src/queues/fixed-queue.ts
src/queues/priority-queue.ts
src/queues/queue-types.ts
src/utility-types.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
src/worker/worker-options.ts
tests/pools/abstract-pool.test.mjs
tests/pools/cluster/dynamic.test.mjs
tests/pools/cluster/fixed.test.mjs
tests/pools/selection-strategies/selection-strategies-utils.test.mjs
tests/pools/selection-strategies/selection-strategies.test.mjs
tests/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.test.mjs
tests/pools/selection-strategies/worker-choice-strategies-context.test.mjs
tests/pools/thread/dynamic.test.mjs
tests/pools/thread/fixed.test.mjs
tests/pools/utils.test.mjs
tests/pools/worker-node.test.mjs
tests/test-types.cjs
tests/test-utils.cjs
tests/utils.test.mjs
tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs
tests/worker-files/cluster/testTaskFunctionObjectsWorker.cjs
tests/worker-files/cluster/testWorker.cjs
tests/worker-files/thread/testMultipleTaskFunctionsWorker.mjs
tests/worker-files/thread/testTaskFunctionObjectsWorker.mjs
tests/worker/abstract-worker.test.mjs
tests/worker/cluster-worker.test.mjs
tests/worker/thread-worker.test.mjs
typedoc.mjs

index 8cdf685f19b20acc912299aa08259b9050eccf86..9dd36b642027c476d0f583427d16e2f838bc59b8 100644 (file)
@@ -1,8 +1,8 @@
 export default {
-  '**/*.{ts,tsx,js,jsx,cjs,mjs}': [
-    'biome format --write',
-    'eslint --cache --fix',
-  ],
-  '**/*.json': ['biome format --write'],
   '**/*.{md,yml,yaml}': ['prettier --cache --write'],
+  // '**/*.{ts,tsx,js,jsx,cjs,mjs}': [
+  //   'biome format --write',
+  //   'eslint --cache --fix',
+  // ],
+  '**/*.json': ['biome format --write'],
 }
index f99a15017965dbd42dafc3ed025f1e51e9a48402..67fab6b32c6deefa2caf9e3469270b1e52a757fd 100644 (file)
@@ -1,7 +1,7 @@
 const TaskFunctions = {
-  jsonIntegerSerialization: 'jsonIntegerSerialization',
-  fibonacci: 'fibonacci',
   factorial: 'factorial',
+  fibonacci: 'fibonacci',
+  jsonIntegerSerialization: 'jsonIntegerSerialization',
   readWriteFiles: 'readWriteFiles',
 }
 
index f212c7b2eaa31e74629a42d86120bdbbf4e1e1a4..30eb1e2b9215cb48435161d9f94dd3a49746121f 100644 (file)
@@ -6,6 +6,7 @@ const {
   rmSync,
   writeFileSync,
 } = require('node:fs')
+
 const { TaskFunctions } = require('./benchmarks-types.cjs')
 
 const jsonIntegerSerialization = n => {
index 6c01efcb348fa7ae11faf07131a3ea4d307cde0a..7198beb067c5583cdc4fd89e1fe3f2cba5012275 100644 (file)
@@ -1,5 +1,4 @@
 import { strictEqual } from 'node:assert'
-
 import { bench, clear, group, run } from 'tatami-ng'
 
 import {
@@ -16,24 +15,15 @@ import { executeTaskFunction } from './benchmarks-utils.cjs'
 
 const buildPoolifierPool = (workerType, poolType, poolSize, poolOptions) => {
   switch (poolType) {
-    case PoolTypes.fixed:
+    case PoolTypes.dynamic:
       switch (workerType) {
-        case WorkerTypes.thread:
-          return new FixedThreadPool(
-            poolSize,
-            './benchmarks/internal/thread-worker.mjs',
-            poolOptions
-          )
         case WorkerTypes.cluster:
-          return new FixedClusterPool(
+          return new DynamicClusterPool(
+            Math.floor(poolSize / 2),
             poolSize,
             './benchmarks/internal/cluster-worker.cjs',
             poolOptions
           )
-      }
-      break
-    case PoolTypes.dynamic:
-      switch (workerType) {
         case WorkerTypes.thread:
           return new DynamicThreadPool(
             Math.floor(poolSize / 2),
@@ -41,13 +31,22 @@ const buildPoolifierPool = (workerType, poolType, poolSize, poolOptions) => {
             './benchmarks/internal/thread-worker.mjs',
             poolOptions
           )
+      }
+      break
+    case PoolTypes.fixed:
+      switch (workerType) {
         case WorkerTypes.cluster:
-          return new DynamicClusterPool(
-            Math.floor(poolSize / 2),
+          return new FixedClusterPool(
             poolSize,
             './benchmarks/internal/cluster-worker.cjs',
             poolOptions
           )
+        case WorkerTypes.thread:
+          return new FixedThreadPool(
+            poolSize,
+            './benchmarks/internal/thread-worker.mjs',
+            poolOptions
+          )
       }
       break
   }
@@ -146,9 +145,9 @@ export const convertTatamiNgToBmf = report => {
       return {
         [name]: {
           latency: {
-            value: stats?.avg,
             lower_value: stats?.min,
             upper_value: stats?.max,
+            value: stats?.avg,
           },
           throughput: {
             value: stats?.iter,
index 8bae6aaf7794ce3d181547a1401ca87455be2c02..78f22a8451109e9f8785f899e46f0af5c150ae3b 100644 (file)
@@ -25,15 +25,15 @@ let benchmarkReport
 
 switch (
   parseArgs({
+    allowPositionals: true,
     args: process.argv,
     options: {
       type: {
-        type: 'string',
         short: 't',
+        type: 'string',
       },
     },
     strict: true,
-    allowPositionals: true,
   }).values.type
 ) {
   case 'tatami-ng':
index 40d614c7364fe17feaa41bec8c3742842f450832..757c6961ec09f701c4db37de3cbd8a78c43f8cab 100644 (file)
@@ -1,7 +1,8 @@
 const { isPrimary } = require('node:cluster')
+
 const { ClusterWorker } = require('../../lib/index.cjs')
-const { executeTaskFunction } = require('../benchmarks-utils.cjs')
 const { TaskFunctions } = require('../benchmarks-types.cjs')
+const { executeTaskFunction } = require('../benchmarks-utils.cjs')
 
 const taskFunction = data => {
   data = data || {}
index 189a5e6c8f05da89f70fe25e0ffe8dc5a8af7f64..09679b0c1c0ae099272978ac16c9771612c5e22f 100644 (file)
@@ -1,5 +1,4 @@
 import { randomInt } from 'node:crypto'
-
 import { bench, group, run } from 'tatami-ng'
 
 /**
index 517ded5d66352a4a4eedd25072fc88b6efd824b8..eb8a8c3301b850d6321fd4bbfb94a44d998eb910 100644 (file)
@@ -2,7 +2,7 @@ import cspellConfigs from '@cspell/eslint-plugin/configs'
 import js from '@eslint/js'
 import { defineFlatConfig } from 'eslint-define-config'
 import jsdoc from 'eslint-plugin-jsdoc'
-import simpleImportSort from 'eslint-plugin-simple-import-sort'
+import perfectionist from 'eslint-plugin-perfectionist'
 import globals from 'globals'
 import neostandard, { plugins } from 'neostandard'
 
@@ -20,12 +20,13 @@ export default defineFlatConfig([
       'jsdoc/check-tag-names': [
         'warn',
         {
-          typed: true,
           definedTags: ['defaultValue', 'experimental', 'typeParam'],
+          typed: true,
         },
       ],
     },
   },
+
   ...plugins['typescript-eslint'].config(
     {
       extends: [
@@ -45,9 +46,6 @@ export default defineFlatConfig([
     }
   ),
   {
-    plugins: {
-      'simple-import-sort': simpleImportSort,
-    },
     rules: {
       '@cspell/spellchecker': [
         'warn',
@@ -66,15 +64,14 @@ export default defineFlatConfig([
           },
         },
       ],
-      'simple-import-sort/imports': 'error',
-      'simple-import-sort/exports': 'error',
     },
   },
+  perfectionist.configs['recommended-natural'],
   ...neostandard({
-    ts: true,
     globals: {
       ...globals.mocha,
     },
+    ts: true,
   }),
   {
     files: [
@@ -88,27 +85,27 @@ export default defineFlatConfig([
   {
     files: ['examples/**/*.ts'],
     rules: {
+      '@typescript-eslint/no-redundant-type-constituents': 'off',
+      '@typescript-eslint/no-unnecessary-type-assertion': 'off',
       '@typescript-eslint/no-unsafe-argument': 'off',
-      '@typescript-eslint/no-unsafe-call': 'off',
-      '@typescript-eslint/no-unsafe-return': 'off',
       '@typescript-eslint/no-unsafe-assignment': 'off',
+      '@typescript-eslint/no-unsafe-call': 'off',
       '@typescript-eslint/no-unsafe-member-access': 'off',
-      '@typescript-eslint/no-unnecessary-type-assertion': 'off',
-      '@typescript-eslint/no-redundant-type-constituents': 'off',
-      '@typescript-eslint/return-await': 'off',
+      '@typescript-eslint/no-unsafe-return': 'off',
       '@typescript-eslint/restrict-template-expressions': 'off',
+      '@typescript-eslint/return-await': 'off',
     },
   },
   {
     files: ['examples/**/*.js', 'examples/**/*.cjs'],
     rules: {
+      '@typescript-eslint/no-require-imports': 'off',
       'n/no-missing-import': [
         'error',
         {
           allowModules: ['ws'],
         },
       ],
-      '@typescript-eslint/no-require-imports': 'off',
     },
   },
   // benchmarks specific configuration
@@ -122,8 +119,8 @@ export default defineFlatConfig([
   {
     files: ['tests/**/*.js', 'tests/**/*.mjs', 'tests/**/*.cjs'],
     rules: {
-      '@typescript-eslint/no-require-imports': 'off',
       '@typescript-eslint/no-empty-function': 'off',
+      '@typescript-eslint/no-require-imports': 'off',
     },
   },
 ])
index ac361d08fd7e1913937ba84bd122f403da86bc0b..7a1b0a0dc205db09d23badc01996a27c23b82fbc 100644 (file)
@@ -1,8 +1,8 @@
 'use strict'
 const {
+  availableParallelism,
   DynamicThreadPool,
   PoolEvents,
-  availableParallelism,
 } = require('poolifier')
 
 const pool = new DynamicThreadPool(
@@ -10,8 +10,8 @@ const pool = new DynamicThreadPool(
   availableParallelism(),
   './yourWorker.js',
   {
-    onlineHandler: () => console.info('worker is online'),
     errorHandler: e => console.error(e),
+    onlineHandler: () => console.info('worker is online'),
   }
 )
 let poolFull = 0
index ac2c3bee4b3cfae4ef389a71093bf1850f1c4acc..d7f129152234cf6f641f8d7db09c2568ac3b12bf 100644 (file)
@@ -1,13 +1,13 @@
 'use strict'
 const {
+  availableParallelism,
   FixedThreadPool,
   PoolEvents,
-  availableParallelism,
 } = require('poolifier')
 
 const pool = new FixedThreadPool(availableParallelism(), './yourWorker.cjs', {
-  onlineHandler: () => console.info('worker is online'),
   errorHandler: e => console.error(e),
+  onlineHandler: () => console.info('worker is online'),
 })
 let poolReady = 0
 let poolBusy = 0
index 9f92c132c55b4b8dc7824b49043bc26959f3f9a2..80aa4a6e6735a0ab60f3001c8677fbcf7c709d20 100644 (file)
@@ -1,12 +1,12 @@
 'use strict'
-const { FixedThreadPool, availableParallelism } = require('poolifier')
+const { availableParallelism, FixedThreadPool } = require('poolifier')
 
 const pool = new FixedThreadPool(
   availableParallelism(),
   './multiFunctionWorker.cjs',
   {
-    onlineHandler: () => console.info('worker is online'),
     errorHandler: e => console.error(e),
+    onlineHandler: () => console.info('worker is online'),
   }
 )
 
index 696671eaeb8e0e81b7b27ee1119704b289e2afd2..1103c4ef9378abf550bac5fb22310b3e079a8055 100644 (file)
@@ -1,8 +1,9 @@
 import { availableParallelism } from 'poolifier'
 
-import { httpClientPool } from './pool.js'
 import type { WorkerResponse } from './types.js'
 
+import { httpClientPool } from './pool.js'
+
 const parallelism = availableParallelism() * 2
 const requestUrl = 'http://localhost:8080/'
 
index 83bec2fd5ac1dc501a4c6b8cc68ca56721d170c5..e0d641732cc83e001b885dafc12ec5ad60c82f36 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, DynamicThreadPool } from 'poolifier'
 
 import type { WorkerData, WorkerResponse } from './types.js'
@@ -16,11 +15,11 @@ export const httpClientPool = new DynamicThreadPool<WorkerData, WorkerResponse>(
   workerFile,
   {
     enableTasksQueue: true,
-    tasksQueueOptions: {
-      concurrency: 8,
-    },
     errorHandler: (e: Error) => {
       console.error('Thread worker error:', e)
     },
+    tasksQueueOptions: {
+      concurrency: 8,
+    },
   }
 )
index d21087dd0d24b7eb0b79e2c99d507239f3ddd474..36df0cd35ae5e540612a800c1de7fdea549c0cfb 100644 (file)
@@ -1,15 +1,14 @@
-import type { URL } from 'node:url'
-
 import type { AxiosRequestConfig } from 'axios'
+import type { URL } from 'node:url'
 import type {
   RequestInfo as NodeFetchRequestInfo,
   RequestInit as NodeFetchRequestInit,
 } from 'node-fetch'
 
 export interface WorkerData {
-  input: URL | RequestInfo | NodeFetchRequestInfo
-  init?: RequestInit | NodeFetchRequestInit
   axiosRequestConfig?: AxiosRequestConfig
+  init?: NodeFetchRequestInit | RequestInit
+  input: NodeFetchRequestInfo | RequestInfo | URL
 }
 
 export interface WorkerResponse {
index 95917d64207d4d846353be93b6560b9ff6b21e9d..a470032695ba4b5ec6b07960d221e7767d8dbaf6 100644 (file)
@@ -10,21 +10,21 @@ import type { WorkerData, WorkerResponse } from './types.js'
 class HttpClientWorker extends ThreadWorker<WorkerData, WorkerResponse> {
   public constructor () {
     super({
-      node_fetch: async (workerData?: WorkerData) => {
-        const response = await nodeFetch(
+      axios: async (workerData?: WorkerData) => {
+        const response = await axios({
+          method: 'get',
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          workerData!.input as URL | NodeFetchRequestInfo,
-          workerData?.init as NodeFetchRequestInit
-        )
-        // The response is not structured-cloneable, so we return the response text body instead.
+          url: workerData!.input as string,
+          ...workerData?.axiosRequestConfig,
+        })
         return {
-          text: await response.text(),
+          text: response.data,
         }
       },
       fetch: async (workerData?: WorkerData) => {
         const response = await fetch(
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          workerData!.input as URL | RequestInfo,
+          workerData!.input as RequestInfo | URL,
           workerData?.init as RequestInit
         )
         // The response is not structured-cloneable, so we return the response text body instead.
@@ -32,15 +32,15 @@ class HttpClientWorker extends ThreadWorker<WorkerData, WorkerResponse> {
           text: await response.text(),
         }
       },
-      axios: async (workerData?: WorkerData) => {
-        const response = await axios({
-          method: 'get',
+      node_fetch: async (workerData?: WorkerData) => {
+        const response = await nodeFetch(
           // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          url: workerData!.input as string,
-          ...workerData?.axiosRequestConfig,
-        })
+          workerData!.input as NodeFetchRequestInfo | URL,
+          workerData?.init as NodeFetchRequestInit
+        )
+        // The response is not structured-cloneable, so we return the response text body instead.
         return {
-          text: response.data,
+          text: await response.text(),
         }
       },
     })
index 2fd3760732d9afd8edf71a3712e6e8b79d771300..913837748f57194309b0e7bde8e57f6b16010a75 100644 (file)
@@ -3,27 +3,27 @@ import { defineConfig } from 'rollup'
 import del from 'rollup-plugin-delete'
 
 export default defineConfig({
+  external: ['express', /^node:*/, 'poolifier'],
   input: ['./src/main.ts', './src/worker.ts'],
-  strictDeprecations: true,
   output: [
     {
-      format: 'cjs',
+      chunkFileNames: '[name]-[hash].cjs',
       dir: './dist',
-      sourcemap: true,
       entryFileNames: '[name].cjs',
-      chunkFileNames: '[name]-[hash].cjs',
+      format: 'cjs',
+      sourcemap: true,
     },
     {
-      format: 'esm',
       dir: './dist',
+      format: 'esm',
       sourcemap: true,
     },
   ],
-  external: ['express', /^node:*/, 'poolifier'],
   plugins: [
     typescript(),
     del({
       targets: ['./dist/*'],
     }),
   ],
+  strictDeprecations: true,
 })
index bde791703bd6e636b0484163dc5bee6845fcf31a..355c5a6b5df08312564d5822004c2e77149acfe9 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, FixedClusterPool } from 'poolifier'
 
 import type { WorkerData, WorkerResponse } from './types.js'
@@ -15,6 +14,9 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
   workerFile,
   {
     enableEvents: false,
+    errorHandler: (e: Error) => {
+      console.error('Cluster worker error:', e)
+    },
     onlineHandler: () => {
       pool
         .execute({ port: 8080 })
@@ -30,8 +32,5 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
           console.error('Express failed to start in cluster worker:', error)
         })
     },
-    errorHandler: (e: Error) => {
-      console.error('Cluster worker error:', e)
-    },
   }
 )
index 7d6c174e18b145414eb947132e511e92d18ff7ba..efaa31f8e66132e6e8dc2964f533135aa3d6e9f1 100644 (file)
@@ -3,6 +3,6 @@ export interface WorkerData {
 }
 
 export interface WorkerResponse {
-  status: boolean
   port?: number
+  status: boolean
 }
index 6c0aad8c43f340b0df7423eecb7f17deaf835072..35a08240094f1f54d42833a7b581bd3b4d352caf 100644 (file)
@@ -7,9 +7,7 @@ import { ClusterWorker } from 'poolifier'
 import type { WorkerData, WorkerResponse } from './types.js'
 
 class ExpressWorker extends ClusterWorker<WorkerData, WorkerResponse> {
-  private static server: Server
-
-  private static readonly factorial = (n: number | bigint): bigint => {
+  private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
@@ -22,6 +20,8 @@ class ExpressWorker extends ClusterWorker<WorkerData, WorkerResponse> {
     }
   }
 
+  private static server: Server
+
   private static readonly startExpress = (
     workerData?: WorkerData
   ): WorkerResponse => {
@@ -54,8 +54,8 @@ class ExpressWorker extends ClusterWorker<WorkerData, WorkerResponse> {
       )
     })
     return {
-      status: true,
       port: listenerPort ?? port,
+      status: true,
     }
   }
 
index cd2a4a98179e0f9e93218440cc39cd44c4ae11db..06e95db7cd64164bde2f7d57925aa34e484a45c7 100644 (file)
@@ -3,31 +3,31 @@ import { defineConfig } from 'rollup'
 import del from 'rollup-plugin-delete'
 
 export default defineConfig({
+  external: ['express', /^node:*/, 'poolifier'],
   input: [
     './src/main.ts',
     './src/express-worker.ts',
     './src/request-handler-worker.ts',
   ],
-  strictDeprecations: true,
   output: [
     {
-      format: 'cjs',
+      chunkFileNames: '[name]-[hash].cjs',
       dir: './dist',
-      sourcemap: true,
       entryFileNames: '[name].cjs',
-      chunkFileNames: '[name]-[hash].cjs',
+      format: 'cjs',
+      sourcemap: true,
     },
     {
-      format: 'esm',
       dir: './dist',
+      format: 'esm',
       sourcemap: true,
     },
   ],
-  external: ['express', /^node:*/, 'poolifier'],
   plugins: [
     typescript(),
     del({
       targets: ['./dist/*'],
     }),
   ],
+  strictDeprecations: true,
 })
index 2089b3ef928d95837fafdeaf106dcd33a4bde05c..053a25ab47f0678901ef420aeb3892daf79b440d 100644 (file)
@@ -24,16 +24,17 @@ class ExpressWorker extends ClusterWorker<
   ClusterWorkerData,
   ClusterWorkerResponse
 > {
-  private static server: Server
   private static requestHandlerPool: DynamicThreadPool<
     ThreadWorkerData<DataPayload>,
     ThreadWorkerResponse<DataPayload>
   >
 
+  private static server: Server
+
   private static readonly startExpress = (
     workerData?: ClusterWorkerData
   ): ClusterWorkerResponse => {
-    const { port, workerFile, minWorkers, maxWorkers, ...poolOptions } =
+    const { maxWorkers, minWorkers, port, workerFile, ...poolOptions } =
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       workerData!
 
@@ -79,8 +80,8 @@ class ExpressWorker extends ClusterWorker<
       )
     })
     return {
-      status: true,
       port: listenerPort ?? port,
+      status: true,
     }
   }
 
index 3abecad8be99d83cc8e8e8ff72dc9a504da85a36..60c724889173bf588f8cf15d72a8ad158fdee579 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, FixedClusterPool } from 'poolifier'
 
 import type { ClusterWorkerData, ClusterWorkerResponse } from './types.js'
@@ -20,22 +19,25 @@ const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
   expressWorkerFile,
   {
     enableEvents: false,
+    errorHandler: (e: Error) => {
+      console.error('Cluster worker error:', e)
+    },
     onlineHandler: () => {
       pool
         .execute({
-          port: 8080,
+          enableTasksQueue: true,
+          errorHandler: (e: Error) => {
+            console.error('Thread worker error:', e)
+          },
           maxWorkers:
             Math.round(availableParallelism() / 4) < 1
               ? 1
               : Math.round(availableParallelism() / 4),
-          workerFile: requestHandlerWorkerFile,
-          enableTasksQueue: true,
+          port: 8080,
           tasksQueueOptions: {
             concurrency: 8,
           },
-          errorHandler: (e: Error) => {
-            console.error('Thread worker error:', e)
-          },
+          workerFile: requestHandlerWorkerFile,
         })
         .then(response => {
           if (response.status) {
@@ -49,8 +51,5 @@ const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
           console.error('Express failed to start in cluster worker:', error)
         })
     },
-    errorHandler: (e: Error) => {
-      console.error('Cluster worker error:', e)
-    },
   }
 )
index 5297de12846601c580c09773eee7e8bee34163c3..aac0d8d08f57e27a1716d3696cb25d833ecc1725 100644 (file)
@@ -10,7 +10,7 @@ class RequestHandlerWorker<
   Data extends ThreadWorkerData<DataPayload>,
   Response extends ThreadWorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: number | bigint): bigint => {
+  private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
index 76a9133e60f1c7d462bdfaf52b7eb54d328b218c..8b4e0f23c721f2ad6a4ddb1389107f9670c9e94b 100644 (file)
@@ -1,15 +1,15 @@
 import type { ThreadPoolOptions } from 'poolifier'
 
 export interface ClusterWorkerData extends ThreadPoolOptions {
+  maxWorkers?: number
+  minWorkers?: number
   port: number
   workerFile: string
-  minWorkers?: number
-  maxWorkers?: number
 }
 
 export interface ClusterWorkerResponse {
-  status: boolean
   port?: number
+  status: boolean
 }
 
 export interface DataPayload {
index 615143646507d8e2f33922426384a19e8e5e906c..d15797935b7ea18715aeedb37ef3304e3ec57b48 100644 (file)
@@ -1,6 +1,5 @@
-import { exit } from 'node:process'
-
 import express, { type Express, type Request, type Response } from 'express'
+import { exit } from 'node:process'
 
 import { requestHandlerPool } from './pool.js'
 
index 29bf4ef7a2be91fbd584429242f5321e722064ba..f4502c43353cb8d0a6e9b032b03ac4f0e3199c5b 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, DynamicThreadPool } from 'poolifier'
 
 import type { BodyPayload, WorkerData, WorkerResponse } from './types.js'
@@ -15,10 +14,10 @@ export const requestHandlerPool = new DynamicThreadPool<
   WorkerResponse<BodyPayload>
 >(1, availableParallelism(), workerFile, {
   enableTasksQueue: true,
-  tasksQueueOptions: {
-    concurrency: 8,
-  },
   errorHandler: (e: Error) => {
     console.error('Thread worker error:', e)
   },
+  tasksQueueOptions: {
+    concurrency: 8,
+  },
 })
index c5819f83870432f12f8b9f88b1e1d7d66e6ff452..3e8699bc639a251e88f3d665be6962ca2e2258ce 100644 (file)
@@ -6,7 +6,7 @@ class RequestHandlerWorker<
   Data extends WorkerData<BodyPayload>,
   Response extends WorkerResponse<BodyPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial: (n: number | bigint) => bigint = n => {
+  private static readonly factorial: (n: bigint | number) => bigint = n => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
index 03cbd852fd0daf715346ea35f8aa4f792fe77ba5..93c1d29d8cab1b95c78988d0d62828267f6c0508 100644 (file)
@@ -3,27 +3,27 @@ import { defineConfig } from 'rollup'
 import del from 'rollup-plugin-delete'
 
 export default defineConfig({
+  external: ['fastify', /^node:*/, 'poolifier'],
   input: ['./src/main.ts', './src/worker.ts'],
-  strictDeprecations: true,
   output: [
     {
-      format: 'cjs',
+      chunkFileNames: '[name]-[hash].cjs',
       dir: './dist',
-      sourcemap: true,
       entryFileNames: '[name].cjs',
-      chunkFileNames: '[name]-[hash].cjs',
+      format: 'cjs',
+      sourcemap: true,
     },
     {
-      format: 'esm',
       dir: './dist',
+      format: 'esm',
       sourcemap: true,
     },
   ],
-  external: ['fastify', /^node:*/, 'poolifier'],
   plugins: [
     typescript(),
     del({
       targets: ['./dist/*'],
     }),
   ],
+  strictDeprecations: true,
 })
index 2afc6538abba388d95a8daefd93058c637afa75b..d85b736929adfdb0734c5b78fadf5d0946737588 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, FixedClusterPool } from 'poolifier'
 
 import type { WorkerData, WorkerResponse } from './types.js'
@@ -15,6 +14,9 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
   workerFile,
   {
     enableEvents: false,
+    errorHandler: (e: Error) => {
+      console.error('Cluster worker error:', e)
+    },
     onlineHandler: () => {
       pool
         .execute({ port: 8080 })
@@ -30,8 +32,5 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
           console.error('Fastify failed to start in cluster worker:', error)
         })
     },
-    errorHandler: (e: Error) => {
-      console.error('Cluster worker error:', e)
-    },
   }
 )
index 7d6c174e18b145414eb947132e511e92d18ff7ba..efaa31f8e66132e6e8dc2964f533135aa3d6e9f1 100644 (file)
@@ -3,6 +3,6 @@ export interface WorkerData {
 }
 
 export interface WorkerResponse {
-  status: boolean
   port?: number
+  status: boolean
 }
index 7003e265492b4e9d0c8d9974978e8c996bf7a0d6..c723e207fb2bf7c2d4f78e1858a18587d4900404 100644 (file)
@@ -6,9 +6,7 @@ import { ClusterWorker } from 'poolifier'
 import type { WorkerData, WorkerResponse } from './types.js'
 
 class FastifyWorker extends ClusterWorker<WorkerData, WorkerResponse> {
-  private static fastify: FastifyInstance
-
-  private static readonly factorial = (n: number | bigint): bigint => {
+  private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
@@ -21,6 +19,8 @@ class FastifyWorker extends ClusterWorker<WorkerData, WorkerResponse> {
     }
   }
 
+  private static fastify: FastifyInstance
+
   private static readonly startFastify = async (
     workerData?: WorkerData
   ): Promise<WorkerResponse> => {
@@ -44,8 +44,8 @@ class FastifyWorker extends ClusterWorker<WorkerData, WorkerResponse> {
 
     await FastifyWorker.fastify.listen({ port })
     return {
-      status: true,
       port: (FastifyWorker.fastify.server.address() as AddressInfo).port,
+      status: true,
     }
   }
 
index 166bfd330a00fa3efe91af3eab84e3d2966edd17..4f666be0291aa089bb39b530ad83a4cbbf696bfc 100644 (file)
@@ -1,17 +1,16 @@
-import type { TransferListItem } from 'node:worker_threads'
-
 import type * as fastify from 'fastify'
+import type { TransferListItem } from 'node:worker_threads'
 import type { DynamicThreadPool } from 'poolifier'
 
 import type { ThreadWorkerData, ThreadWorkerResponse } from '../../src/types.ts'
 
 declare module 'fastify' {
   export interface FastifyInstance extends fastify.FastifyInstance {
-    pool: DynamicThreadPool<ThreadWorkerData, ThreadWorkerResponse>
     execute: (
       data?: ThreadWorkerData,
       name?: string,
       transferList?: readonly TransferListItem[]
     ) => Promise<ThreadWorkerResponse>
+    pool: DynamicThreadPool<ThreadWorkerData, ThreadWorkerResponse>
   }
 }
index ae79b9bcf0e0b04525c6ee13ce14ff0728bfc454..617736cbe06a65b97dcf7d4218d5ab6ddd22749b 100644 (file)
@@ -3,31 +3,31 @@ import { defineConfig } from 'rollup'
 import del from 'rollup-plugin-delete'
 
 export default defineConfig({
+  external: ['fastify', 'fastify-plugin', /^node:*/, 'poolifier'],
   input: [
     './src/main.ts',
     './src/fastify-worker.ts',
     './src/request-handler-worker.ts',
   ],
-  strictDeprecations: true,
   output: [
     {
-      format: 'cjs',
+      chunkFileNames: '[name]-[hash].cjs',
       dir: './dist',
-      sourcemap: true,
       entryFileNames: '[name].cjs',
-      chunkFileNames: '[name]-[hash].cjs',
+      format: 'cjs',
+      sourcemap: true,
     },
     {
-      format: 'esm',
       dir: './dist',
+      format: 'esm',
       sourcemap: true,
     },
   ],
-  external: ['fastify', 'fastify-plugin', /^node:*/, 'poolifier'],
   plugins: [
     typescript(),
     del({
       targets: ['./dist/*'],
     }),
   ],
+  strictDeprecations: true,
 })
index 753f6df1872f39d879800d600780e6d598511cd6..ece9b8cf77456eecfc87c303c4d170351e1da59d 100644 (file)
@@ -1,6 +1,6 @@
+import type { FastifyPluginCallback } from 'fastify'
 import type { TransferListItem } from 'node:worker_threads'
 
-import type { FastifyPluginCallback } from 'fastify'
 import fp from 'fastify-plugin'
 import { availableParallelism, DynamicThreadPool } from 'poolifier'
 
@@ -17,12 +17,12 @@ const fastifyPoolifierPlugin: FastifyPluginCallback<FastifyPoolifierOptions> = (
 ) => {
   options = {
     ...{
-      minWorkers: 1,
       maxWorkers: availableParallelism(),
+      minWorkers: 1,
     },
     ...options,
   }
-  const { workerFile, minWorkers, maxWorkers, ...poolOptions } = options
+  const { maxWorkers, minWorkers, workerFile, ...poolOptions } = options
   const pool = new DynamicThreadPool<ThreadWorkerData, ThreadWorkerResponse>(
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     minWorkers!,
index 4a697f3e255def59329478440f5f8d7ca205737e..6b10dbd911a654509afe8874042b2e55e10bd2cb 100644 (file)
@@ -3,9 +3,10 @@ import type { AddressInfo } from 'node:net'
 import Fastify, { type FastifyInstance } from 'fastify'
 import { ClusterWorker } from 'poolifier'
 
-import { fastifyPoolifier } from './fastify-poolifier.js'
 import type { ClusterWorkerData, ClusterWorkerResponse } from './types.js'
 
+import { fastifyPoolifier } from './fastify-poolifier.js'
+
 class FastifyWorker extends ClusterWorker<
   ClusterWorkerData,
   ClusterWorkerResponse
@@ -44,8 +45,8 @@ class FastifyWorker extends ClusterWorker<
 
     await FastifyWorker.fastify.listen({ port })
     return {
-      status: true,
       port: (FastifyWorker.fastify.server.address() as AddressInfo).port,
+      status: true,
     }
   }
 
index 606e77660eafc352dbbd9c4c442436fae646e0f0..60e8655ed7fc30717e50c48dc6121eeeab1e4df8 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, FixedClusterPool } from 'poolifier'
 
 import type { ClusterWorkerData, ClusterWorkerResponse } from './types.js'
@@ -20,22 +19,25 @@ const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
   fastifyWorkerFile,
   {
     enableEvents: false,
+    errorHandler: (e: Error) => {
+      console.error('Cluster worker error:', e)
+    },
     onlineHandler: () => {
       pool
         .execute({
-          port: 8080,
-          workerFile: requestHandlerWorkerFile,
+          enableTasksQueue: true,
+          errorHandler: (e: Error) => {
+            console.error('Thread worker error', e)
+          },
           maxWorkers:
             Math.round(availableParallelism() / 4) < 1
               ? 1
               : Math.round(availableParallelism() / 4),
-          enableTasksQueue: true,
+          port: 8080,
           tasksQueueOptions: {
             concurrency: 8,
           },
-          errorHandler: (e: Error) => {
-            console.error('Thread worker error', e)
-          },
+          workerFile: requestHandlerWorkerFile,
         })
         .then(response => {
           if (response.status) {
@@ -49,8 +51,5 @@ const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
           console.error('Fastify failed to start in cluster worker:', error)
         })
     },
-    errorHandler: (e: Error) => {
-      console.error('Cluster worker error:', e)
-    },
   }
 )
index 5297de12846601c580c09773eee7e8bee34163c3..aac0d8d08f57e27a1716d3696cb25d833ecc1725 100644 (file)
@@ -10,7 +10,7 @@ class RequestHandlerWorker<
   Data extends ThreadWorkerData<DataPayload>,
   Response extends ThreadWorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: number | bigint): bigint => {
+  private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
index 179ac65edd4cd708c4babe4728599a961fe9c1a1..b1a6971b3b2676419f517994d1c21b81c5704f80 100644 (file)
@@ -5,8 +5,8 @@ export interface ClusterWorkerData extends FastifyPoolifierOptions {
 }
 
 export interface ClusterWorkerResponse {
-  status: boolean
   port?: number
+  status: boolean
 }
 
 export interface DataPayload {
@@ -22,7 +22,7 @@ export interface ThreadWorkerResponse<T = unknown> {
 }
 
 export interface FastifyPoolifierOptions extends ThreadPoolOptions {
-  workerFile: string
-  minWorkers?: number
   maxWorkers?: number
+  minWorkers?: number
+  workerFile: string
 }
index 899aa1a028926166853cb383eaf8b2dc7cdebf46..e78ab0d97ae5c9cdd17ad0ddd13b9f00be4a90af 100644 (file)
@@ -1,17 +1,16 @@
-import type { TransferListItem } from 'node:worker_threads'
-
 import type * as fastify from 'fastify'
+import type { TransferListItem } from 'node:worker_threads'
 import type { DynamicThreadPool } from 'poolifier'
 
 import type { WorkerData, WorkerResponse } from '../../src/types.ts'
 
 declare module 'fastify' {
   export interface FastifyInstance extends fastify.FastifyInstance {
-    pool: DynamicThreadPool<WorkerData, WorkerResponse>
     execute: (
       data?: WorkerData,
       name?: string,
       transferList?: readonly TransferListItem[]
     ) => Promise<WorkerResponse>
+    pool: DynamicThreadPool<WorkerData, WorkerResponse>
   }
 }
index 451fcb80b3e12ad89c9022d8b604245adae9f1c1..a9156566ef6025ebf4799e21bbe7e7ba6cd7b7ec 100644 (file)
@@ -1,6 +1,6 @@
+import type { FastifyPluginCallback } from 'fastify'
 import type { TransferListItem } from 'node:worker_threads'
 
-import type { FastifyPluginCallback } from 'fastify'
 import fp from 'fastify-plugin'
 import { availableParallelism, DynamicThreadPool } from 'poolifier'
 
@@ -17,12 +17,12 @@ const fastifyPoolifierPlugin: FastifyPluginCallback<FastifyPoolifierOptions> = (
 ) => {
   options = {
     ...{
-      minWorkers: 1,
       maxWorkers: availableParallelism(),
+      minWorkers: 1,
     },
     ...options,
   }
-  const { workerFile, minWorkers, maxWorkers, ...poolOptions } = options
+  const { maxWorkers, minWorkers, workerFile, ...poolOptions } = options
   const pool = new DynamicThreadPool<WorkerData, WorkerResponse>(
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     minWorkers!,
index d30e913cb8b7ca47cb6f8993cefc28ee6a177803..34dda2678a11ebaa0fe01d1ab9994930507efe9c 100644 (file)
@@ -1,9 +1,8 @@
+import Fastify from 'fastify'
 import { dirname, extname, join } from 'node:path'
 import { exit } from 'node:process'
 import { fileURLToPath } from 'node:url'
 
-import Fastify from 'fastify'
-
 import { fastifyPoolifier } from './fastify-poolifier.js'
 
 /**
@@ -21,14 +20,14 @@ const workerFile = join(
 )
 
 await fastify.register(fastifyPoolifier, {
-  workerFile,
   enableTasksQueue: true,
-  tasksQueueOptions: {
-    concurrency: 8,
-  },
   errorHandler: (e: Error) => {
     fastify.log.error('Thread worker error:', e)
   },
+  tasksQueueOptions: {
+    concurrency: 8,
+  },
+  workerFile,
 })
 
 fastify.all('/api/echo', async request => {
index 97c2c540df9edbf44a01c01f6b8517c3d04a4d73..255a0fcb1cea91d0131e3eee3024b807d8de25df 100644 (file)
@@ -13,7 +13,7 @@ export interface WorkerResponse<T = unknown> {
 }
 
 export interface FastifyPoolifierOptions extends ThreadPoolOptions {
-  workerFile: string
-  minWorkers?: number
   maxWorkers?: number
+  minWorkers?: number
+  workerFile: string
 }
index c5819f83870432f12f8b9f88b1e1d7d66e6ff452..3e8699bc639a251e88f3d665be6962ca2e2258ce 100644 (file)
@@ -6,7 +6,7 @@ class RequestHandlerWorker<
   Data extends WorkerData<BodyPayload>,
   Response extends WorkerResponse<BodyPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial: (n: number | bigint) => bigint = n => {
+  private static readonly factorial: (n: bigint | number) => bigint = n => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
index 1be0691a7aa7bf42423530d25d82994e4ce7fa6e..c339b70558820681a8782ec13dde402b847f9f38 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import {
   availableParallelism,
   DynamicThreadPool,
@@ -18,12 +17,12 @@ const fixedPool = new FixedThreadPool<MyData, MyResponse>(
   availableParallelism(),
   workerFile,
   {
-    onlineHandler: () => {
-      console.info('Worker is online')
-    },
     errorHandler: (e: Error) => {
       console.error(e)
     },
+    onlineHandler: () => {
+      console.info('Worker is online')
+    },
   }
 )
 
@@ -34,12 +33,12 @@ const dynamicPool = new DynamicThreadPool<MyData, MyResponse>(
   availableParallelism(),
   workerFile,
   {
-    onlineHandler: () => {
-      console.info('Worker is online')
-    },
     errorHandler: (e: Error) => {
       console.error(e)
     },
+    onlineHandler: () => {
+      console.info('Worker is online')
+    },
   }
 )
 
index 4d1f2ea896188b7cfa49aa559dfaaab5f5a28f7c..f9f4d78e9abf40bcc284cdb30331f8ad6e1c84a3 100644 (file)
@@ -8,21 +8,21 @@ const smtpClientPoolPromises = new Set<Promise<SMTPTransport.SentMessageInfo>>()
 for (const to of tos) {
   smtpClientPoolPromises.add(
     smtpClientPool.execute({
-      smtpTransport: {
-        host: 'smtp.domain.tld',
-        port: 465,
-        secure: true,
-        auth: {
-          user: 'REPLACE-WITH-YOUR-ALIAS@DOMAIN.TLD',
-          pass: 'REPLACE-WITH-YOUR-GENERATED-PASSWORD',
-        },
-      },
       mail: {
         from: '"Foo" <foo@domain.tld>',
-        to,
+        html: '<b>Hello world?</b>',
         subject: 'Hello',
         text: 'Hello world?',
-        html: '<b>Hello world?</b>',
+        to,
+      },
+      smtpTransport: {
+        auth: {
+          pass: 'REPLACE-WITH-YOUR-GENERATED-PASSWORD',
+          user: 'REPLACE-WITH-YOUR-ALIAS@DOMAIN.TLD',
+        },
+        host: 'smtp.domain.tld',
+        port: 465,
+        secure: true,
       },
     })
   )
index 747837037499e5665e0f6482cc7f4946be08882a..2e9f788b122168759ca68973e8d2be0794b4322c 100644 (file)
@@ -1,7 +1,7 @@
+import type SMTPTransport from 'nodemailer/lib/smtp-transport/index.js'
+
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
-import type SMTPTransport from 'nodemailer/lib/smtp-transport/index.js'
 import { availableParallelism, DynamicThreadPool } from 'poolifier'
 
 import type { WorkerData } from './types.js'
@@ -16,10 +16,10 @@ export const smtpClientPool = new DynamicThreadPool<
   SMTPTransport.SentMessageInfo
 >(0, availableParallelism(), workerFile, {
   enableTasksQueue: true,
-  tasksQueueOptions: {
-    concurrency: 8,
-  },
   errorHandler: (e: Error) => {
     console.error('Thread worker error:', e)
   },
+  tasksQueueOptions: {
+    concurrency: 8,
+  },
 })
index 7287ad5490fd22f250c641ff18bf3b86f8d90249..41396c877870fc59113dceae8c19098fd0bd8f28 100644 (file)
@@ -2,6 +2,6 @@ import type Mail from 'nodemailer/lib/mailer/index.js'
 import type SMTPTransport from 'nodemailer/lib/smtp-transport/index.js'
 
 export interface WorkerData {
-  smtpTransport: SMTPTransport.Options
   mail: Mail.Options
+  smtpTransport: SMTPTransport.Options
 }
index e2667b326f08fc718f93e9b9f43d881c1878a1ed..dd8f908bb7c68772cb75a655f1d00daa28459762 100644 (file)
@@ -1,5 +1,6 @@
-import { createTransport } from 'nodemailer'
 import type SMTPTransport from 'nodemailer/lib/smtp-transport/index.js'
+
+import { createTransport } from 'nodemailer'
 import { ThreadWorker } from 'poolifier'
 
 import type { WorkerData } from './types.js'
index 2c73376f6ee4089920e10a12c52560062ef3c2c1..f9825e2bb59486dd37eb28239225fb69c34515d5 100644 (file)
@@ -7,11 +7,11 @@ ws.on('error', console.error)
 ws.on('open', () => {
   for (let i = 0; i < 60; i++) {
     ws.send(
-      JSON.stringify({ type: 'echo', data: { key1: 'value1', key2: 'value2' } })
+      JSON.stringify({ data: { key1: 'value1', key2: 'value2' }, type: 'echo' })
     )
   }
   for (let i = 0; i < 60; i++) {
-    ws.send(JSON.stringify({ type: 'factorial', data: { number: 50000 } }))
+    ws.send(JSON.stringify({ data: { number: 50000 }, type: 'factorial' }))
   }
 })
 
index 4080e1ac74c72bad30d95862cfba92c5bef8be7b..ee1b4c2cf8751b56a7a26d046be1dd7a26474261 100644 (file)
@@ -3,27 +3,27 @@ import { defineConfig } from 'rollup'
 import del from 'rollup-plugin-delete'
 
 export default defineConfig({
+  external: [/^node:*/, 'poolifier', 'ws'],
   input: ['./src/main.ts', './src/worker.ts'],
-  strictDeprecations: true,
   output: [
     {
-      format: 'cjs',
+      chunkFileNames: '[name]-[hash].cjs',
       dir: './dist',
-      sourcemap: true,
       entryFileNames: '[name].cjs',
-      chunkFileNames: '[name]-[hash].cjs',
+      format: 'cjs',
+      sourcemap: true,
     },
     {
-      format: 'esm',
       dir: './dist',
+      format: 'esm',
       sourcemap: true,
     },
   ],
-  external: [/^node:*/, 'poolifier', 'ws'],
   plugins: [
     typescript(),
     del({
       targets: ['./dist/*'],
     }),
   ],
+  strictDeprecations: true,
 })
index 1950fec3b6287c24e4f5afda45ad09437d504f69..654be2ba894ef345bbc1430f2c4f257dcaeccfa4 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, FixedClusterPool } from 'poolifier'
 
 import type { WorkerData, WorkerResponse } from './types.js'
@@ -15,6 +14,9 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
   workerFile,
   {
     enableEvents: false,
+    errorHandler: (e: Error) => {
+      console.error('Cluster worker error', e)
+    },
     onlineHandler: () => {
       pool
         .execute({ port: 8080 })
@@ -33,8 +35,5 @@ const pool = new FixedClusterPool<WorkerData, WorkerResponse>(
           )
         })
     },
-    errorHandler: (e: Error) => {
-      console.error('Cluster worker error', e)
-    },
   }
 )
index 922914a1d8f7a04ce27052722cb72582ba323037..851e47e69f05900ff0b8cdf453428188ead3ce4a 100644 (file)
@@ -4,8 +4,8 @@ export enum MessageType {
 }
 
 export interface MessagePayload<T = unknown> {
-  type: MessageType
   data: T
+  type: MessageType
 }
 
 export interface DataPayload {
@@ -17,6 +17,6 @@ export interface WorkerData {
 }
 
 export interface WorkerResponse {
-  status: boolean
   port?: number
+  status: boolean
 }
index c1db83dadc5a10cbf5b056d34bd04c80f1360b32..a5577d7ae57e26d7977737c98a90bbbbeebb0891 100644 (file)
@@ -10,9 +10,7 @@ import {
 } from './types.js'
 
 class WebSocketServerWorker extends ClusterWorker<WorkerData, WorkerResponse> {
-  private static wss: WebSocketServer
-
-  private static readonly factorial = (n: number | bigint): bigint => {
+  private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
@@ -40,15 +38,15 @@ class WebSocketServerWorker extends ClusterWorker<WorkerData, WorkerResponse> {
     WebSocketServerWorker.wss.on('connection', ws => {
       ws.on('error', console.error)
       ws.on('message', (message: RawData) => {
-        const { type, data } = JSON.parse(
+        const { data, type } = JSON.parse(
           message.toString()
         ) as MessagePayload<DataPayload>
         switch (type) {
           case MessageType.echo:
             ws.send(
               JSON.stringify({
-                type: MessageType.echo,
                 data,
+                type: MessageType.echo,
               })
             )
             break
@@ -56,11 +54,11 @@ class WebSocketServerWorker extends ClusterWorker<WorkerData, WorkerResponse> {
             ws.send(
               JSON.stringify(
                 {
-                  type: MessageType.factorial,
                   data: {
                     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                     number: WebSocketServerWorker.factorial(data.number!),
                   },
+                  type: MessageType.factorial,
                 },
                 (_, v: unknown) => (typeof v === 'bigint' ? v.toString() : v)
               )
@@ -70,11 +68,13 @@ class WebSocketServerWorker extends ClusterWorker<WorkerData, WorkerResponse> {
       })
     })
     return {
-      status: true,
       port: WebSocketServerWorker.wss.options.port,
+      status: true,
     }
   }
 
+  private static wss: WebSocketServer
+
   public constructor () {
     super(WebSocketServerWorker.startWebSocketServer, {
       killHandler: () => {
index 2c73376f6ee4089920e10a12c52560062ef3c2c1..f9825e2bb59486dd37eb28239225fb69c34515d5 100644 (file)
@@ -7,11 +7,11 @@ ws.on('error', console.error)
 ws.on('open', () => {
   for (let i = 0; i < 60; i++) {
     ws.send(
-      JSON.stringify({ type: 'echo', data: { key1: 'value1', key2: 'value2' } })
+      JSON.stringify({ data: { key1: 'value1', key2: 'value2' }, type: 'echo' })
     )
   }
   for (let i = 0; i < 60; i++) {
-    ws.send(JSON.stringify({ type: 'factorial', data: { number: 50000 } }))
+    ws.send(JSON.stringify({ data: { number: 50000 }, type: 'factorial' }))
   }
 })
 
index fb87823561344ea583371ece1829e0b92b8c6c2b..aa309de8092c45b14c0c349e12aaebf91edc88e0 100644 (file)
@@ -3,31 +3,31 @@ import { defineConfig } from 'rollup'
 import del from 'rollup-plugin-delete'
 
 export default defineConfig({
+  external: [/^node:*/, 'poolifier', 'ws'],
   input: [
     './src/main.ts',
     './src/websocket-server-worker.ts',
     './src/request-handler-worker.ts',
   ],
-  strictDeprecations: true,
   output: [
     {
-      format: 'cjs',
+      chunkFileNames: '[name]-[hash].cjs',
       dir: './dist',
-      sourcemap: true,
       entryFileNames: '[name].cjs',
-      chunkFileNames: '[name]-[hash].cjs',
+      format: 'cjs',
+      sourcemap: true,
     },
     {
-      format: 'esm',
       dir: './dist',
+      format: 'esm',
       sourcemap: true,
     },
   ],
-  external: [/^node:*/, 'poolifier', 'ws'],
   plugins: [
     typescript(),
     del({
       targets: ['./dist/*'],
     }),
   ],
+  strictDeprecations: true,
 })
index 005680f5362a548e36dbe784ed425f8ff4cd96ee..13f78249fefa83c59f722591aca25ad06257d91c 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, FixedClusterPool } from 'poolifier'
 
 import type { ClusterWorkerData, ClusterWorkerResponse } from './types.js'
@@ -20,22 +19,25 @@ const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
   webSocketServerWorkerFile,
   {
     enableEvents: false,
+    errorHandler: (e: Error) => {
+      console.error('Cluster worker error', e)
+    },
     onlineHandler: () => {
       pool
         .execute({
-          port: 8080,
+          enableTasksQueue: true,
+          errorHandler: (e: Error) => {
+            console.error('Thread worker error:', e)
+          },
           maxWorkers:
             Math.round(availableParallelism() / 4) < 1
               ? 1
               : Math.round(availableParallelism() / 4),
-          workerFile: requestHandlerWorkerFile,
-          enableTasksQueue: true,
+          port: 8080,
           tasksQueueOptions: {
             concurrency: 8,
           },
-          errorHandler: (e: Error) => {
-            console.error('Thread worker error:', e)
-          },
+          workerFile: requestHandlerWorkerFile,
         })
         .then(response => {
           if (response.status) {
@@ -52,8 +54,5 @@ const pool = new FixedClusterPool<ClusterWorkerData, ClusterWorkerResponse>(
           )
         })
     },
-    errorHandler: (e: Error) => {
-      console.error('Cluster worker error', e)
-    },
   }
 )
index 444c5179f7de1564d36449fcc64415492e57f74d..539a03ff478d2a6bd8a2b9a8eb6cd13bd349b0c7 100644 (file)
@@ -10,7 +10,7 @@ class RequestHandlerWorker<
   Data extends ThreadWorkerData<DataPayload>,
   Response extends ThreadWorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: number | bigint): bigint => {
+  private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
index d52dbf2396569595aae7918775f208bdcd54154f..80c01eceb61ff9eceba8574369c5a2ed05d4f400 100644 (file)
@@ -6,8 +6,8 @@ export enum MessageType {
 }
 
 export interface MessagePayload<T = unknown> {
-  type: MessageType
   data: T
+  type: MessageType
 }
 
 export interface DataPayload {
@@ -15,15 +15,15 @@ export interface DataPayload {
 }
 
 export interface ClusterWorkerData extends ThreadPoolOptions {
+  maxWorkers?: number
+  minWorkers?: number
   port: number
   workerFile: string
-  minWorkers?: number
-  maxWorkers?: number
 }
 
 export interface ClusterWorkerResponse {
-  status: boolean
   port?: number
+  status: boolean
 }
 
 export interface ThreadWorkerData<T = unknown> {
index 53d71df4061281c9c3ceb8ab2fd525f71a37a3a9..68cbdf2aaae38a1cc079f1605175874f45064f25 100644 (file)
@@ -23,7 +23,6 @@ class WebSocketServerWorker extends ClusterWorker<
   ClusterWorkerData,
   ClusterWorkerResponse
 > {
-  private static wss: WebSocketServer
   private static requestHandlerPool: DynamicThreadPool<
     ThreadWorkerData<DataPayload>,
     ThreadWorkerResponse<DataPayload>
@@ -32,7 +31,7 @@ class WebSocketServerWorker extends ClusterWorker<
   private static readonly startWebSocketServer = (
     workerData?: ClusterWorkerData
   ): ClusterWorkerResponse => {
-    const { port, workerFile, minWorkers, maxWorkers, ...poolOptions } =
+    const { maxWorkers, minWorkers, port, workerFile, ...poolOptions } =
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
       workerData!
 
@@ -55,7 +54,7 @@ class WebSocketServerWorker extends ClusterWorker<
     WebSocketServerWorker.wss.on('connection', ws => {
       ws.on('error', console.error)
       ws.on('message', (message: RawData) => {
-        const { type, data } = JSON.parse(
+        const { data, type } = JSON.parse(
           message.toString()
         ) as MessagePayload<DataPayload>
         switch (type) {
@@ -65,8 +64,8 @@ class WebSocketServerWorker extends ClusterWorker<
               .then(response => {
                 ws.send(
                   JSON.stringify({
-                    type: MessageType.echo,
                     data: response.data,
+                    type: MessageType.echo,
                   })
                 )
                 return undefined
@@ -80,8 +79,8 @@ class WebSocketServerWorker extends ClusterWorker<
                 ws.send(
                   JSON.stringify(
                     {
-                      type: MessageType.factorial,
                       data: response.data,
+                      type: MessageType.factorial,
                     },
                     (_, v: unknown) =>
                       typeof v === 'bigint' ? v.toString() : v
@@ -95,11 +94,13 @@ class WebSocketServerWorker extends ClusterWorker<
       })
     })
     return {
-      status: true,
       port: WebSocketServerWorker.wss.options.port,
+      status: true,
     }
   }
 
+  private static wss: WebSocketServer
+
   public constructor () {
     super(WebSocketServerWorker.startWebSocketServer, {
       killHandler: async () => {
index 2c73376f6ee4089920e10a12c52560062ef3c2c1..f9825e2bb59486dd37eb28239225fb69c34515d5 100644 (file)
@@ -7,11 +7,11 @@ ws.on('error', console.error)
 ws.on('open', () => {
   for (let i = 0; i < 60; i++) {
     ws.send(
-      JSON.stringify({ type: 'echo', data: { key1: 'value1', key2: 'value2' } })
+      JSON.stringify({ data: { key1: 'value1', key2: 'value2' }, type: 'echo' })
     )
   }
   for (let i = 0; i < 60; i++) {
-    ws.send(JSON.stringify({ type: 'factorial', data: { number: 50000 } }))
+    ws.send(JSON.stringify({ data: { number: 50000 }, type: 'factorial' }))
   }
 })
 
index 0a308e989478b5e3a00f613c0237f961280dd1b3..38ea9d9563a176a1770ed7f2f5ec75e457fd6a9d 100644 (file)
@@ -17,7 +17,7 @@ const emptyFunction = (): void => {
 wss.on('connection', ws => {
   ws.on('error', console.error)
   ws.on('message', (message: RawData) => {
-    const { type, data } = JSON.parse(
+    const { data, type } = JSON.parse(
       message.toString()
     ) as MessagePayload<DataPayload>
     switch (type) {
@@ -27,8 +27,8 @@ wss.on('connection', ws => {
           .then(response => {
             ws.send(
               JSON.stringify({
-                type: MessageType.echo,
                 data: response.data,
+                type: MessageType.echo,
               })
             )
             return undefined
@@ -42,8 +42,8 @@ wss.on('connection', ws => {
             ws.send(
               JSON.stringify(
                 {
-                  type: MessageType.factorial,
                   data: response.data,
+                  type: MessageType.factorial,
                 },
                 (_, v: unknown) => (typeof v === 'bigint' ? v.toString() : v)
               )
index 1cefa60cb421c78a3ecc7312ebdb45ea0e236243..e88a5181f87e661890d687006300cd131d14609a 100644 (file)
@@ -1,6 +1,5 @@
 import { dirname, extname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
 import { availableParallelism, DynamicThreadPool } from 'poolifier'
 
 import type { DataPayload, WorkerData, WorkerResponse } from './types.js'
@@ -15,10 +14,10 @@ export const requestHandlerPool = new DynamicThreadPool<
   WorkerResponse<DataPayload>
 >(1, availableParallelism(), workerFile, {
   enableTasksQueue: true,
-  tasksQueueOptions: {
-    concurrency: 8,
-  },
   errorHandler: (e: Error) => {
     console.error('Thread worker error:', e)
   },
+  tasksQueueOptions: {
+    concurrency: 8,
+  },
 })
index 4f821e67d521ab9bf07b4a5eb38283b065aa8a15..a930cdbb2654cebd5ad96ac7a15b766361ecf1c6 100644 (file)
@@ -4,8 +4,8 @@ export enum MessageType {
 }
 
 export interface MessagePayload<T = unknown> {
-  type: MessageType
   data: T
+  type: MessageType
 }
 
 export interface DataPayload {
index 2c4de3e2735439e96d0f78b8f322f3a1cbd35af2..575066905615f7db485e547a99b5523608dc1d02 100644 (file)
@@ -6,7 +6,7 @@ class RequestHandlerWorker<
   Data extends WorkerData<DataPayload>,
   Response extends WorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: number | bigint): bigint => {
+  private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
     } else {
index 7ab1fc5649e0c0ec366a67225304e9fc9577e47e..989e2f4fe0e9700ec4dfba8c586e611b4ef68531 100644 (file)
@@ -5,8 +5,8 @@ export interface MyData {
 }
 
 export interface MyResponse {
-  message: string
   data?: MyData
+  message: string
 }
 
 class MyThreadWorker extends ThreadWorker<MyData, MyResponse> {
@@ -19,7 +19,7 @@ class MyThreadWorker extends ThreadWorker<MyData, MyResponse> {
   private async process (data?: MyData): Promise<MyResponse> {
     return await new Promise(resolve => {
       setTimeout(() => {
-        resolve({ message: 'Hello from Worker :)', data })
+        resolve({ data, message: 'Hello from Worker :)' })
       }, 1000)
     })
   }
index a4d52aafc5da46b793981bfd80dcb23c74277375..59c798ab40c73ed4e84c2243a4ad922459905db0 100644 (file)
     "@eslint/js": "^9.9.0",
     "@rollup/plugin-terser": "^0.4.4",
     "@rollup/plugin-typescript": "^11.1.6",
-    "@types/node": "^22.4.1",
+    "@types/node": "^22.5.0",
     "c8": "^10.1.2",
     "cross-env": "^7.0.3",
     "eslint": "^9.9.0",
     "eslint-define-config": "^2.1.0",
     "eslint-plugin-jsdoc": "^50.2.2",
-    "eslint-plugin-simple-import-sort": "^12.1.1",
+    "eslint-plugin-perfectionist": "^3.2.0",
     "expect": "^29.7.0",
     "globals": "^15.9.0",
     "husky": "^9.1.5",
index 154ea75e4201f17e63375637d14acdd7aab6c9e2..e8b476d4889309e5a51b2df6d82411cdfed34e56 100644 (file)
@@ -16,7 +16,7 @@ importers:
         version: 1.8.3
       '@commitlint/cli':
         specifier: ^19.4.0
-        version: 19.4.0(@types/node@22.4.1)(typescript@5.5.4)
+        version: 19.4.0(@types/node@22.5.0)(typescript@5.5.4)
       '@commitlint/config-conventional':
         specifier: ^19.2.2
         version: 19.2.2
@@ -33,8 +33,8 @@ importers:
         specifier: ^11.1.6
         version: 11.1.6(rollup@4.21.0)(tslib@2.6.3)(typescript@5.5.4)
       '@types/node':
-        specifier: ^22.4.1
-        version: 22.4.1
+        specifier: ^22.5.0
+        version: 22.5.0
       c8:
         specifier: ^10.1.2
         version: 10.1.2
@@ -50,9 +50,9 @@ importers:
       eslint-plugin-jsdoc:
         specifier: ^50.2.2
         version: 50.2.2(eslint@9.9.0(jiti@1.21.6))
-      eslint-plugin-simple-import-sort:
-        specifier: ^12.1.1
-        version: 12.1.1(eslint@9.9.0(jiti@1.21.6))
+      eslint-plugin-perfectionist:
+        specifier: ^3.2.0
+        version: 3.2.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)
       expect:
         specifier: ^29.7.0
         version: 29.7.0
@@ -276,8 +276,8 @@ packages:
   '@cspell/dict-companies@3.1.4':
     resolution: {integrity: sha512-y9e0amzEK36EiiKx3VAA+SHQJPpf2Qv5cCt5eTUSggpTkiFkCh6gRKQ97rVlrKh5GJrqinDwYIJtTsxuh2vy2Q==}
 
-  '@cspell/dict-cpp@5.1.12':
-    resolution: {integrity: sha512-6lXLOFIa+k/qBcu0bjaE/Kc6v3sh9VhsDOXD1Dalm3zgd0QIMjp5XBmkpSdCAK3pWCPV0Se7ysVLDfCea1BuXg==}
+  '@cspell/dict-cpp@5.1.14':
+    resolution: {integrity: sha512-DxmlkwDhfPvA2fcYHg46Ly84E3BWwKt2mxUZ41pdgqmzPXdsvoAXbTAgXsRHuHfoHzD6hB1xai9z2JYHeiMqKQ==}
 
   '@cspell/dict-cryptocurrencies@5.0.0':
     resolution: {integrity: sha512-Z4ARIw5+bvmShL+4ZrhDzGhnc9znaAGHOEMaB/GURdS/jdoreEDY34wdN0NtdLHDO5KO7GduZnZyqGdRoiSmYA==}
@@ -300,8 +300,8 @@ packages:
   '@cspell/dict-docker@1.1.7':
     resolution: {integrity: sha512-XlXHAr822euV36GGsl2J1CkBIVg3fZ6879ZOg5dxTIssuhUOCiV2BuzKZmt6aIFmcdPmR14+9i9Xq+3zuxeX0A==}
 
-  '@cspell/dict-dotnet@5.0.2':
-    resolution: {integrity: sha512-UD/pO2A2zia/YZJ8Kck/F6YyDSpCMq0YvItpd4YbtDVzPREfTZ48FjZsbYi4Jhzwfvc6o8R56JusAE58P+4sNQ==}
+  '@cspell/dict-dotnet@5.0.3':
+    resolution: {integrity: sha512-q8+b8YWYv+9Q+AbU3mH/RHE9aovhCuGtMuNSsx+YnTofEhVQkJR3vdrYjhOBg3epIiZVUS83VP0vxPLPa+UTug==}
 
   '@cspell/dict-elixir@4.0.3':
     resolution: {integrity: sha512-g+uKLWvOp9IEZvrIvBPTr/oaO6619uH/wyqypqvwpmnmpjcfi8+/hqZH8YNKt15oviK8k4CkINIqNhyndG9d9Q==}
@@ -333,8 +333,8 @@ packages:
   '@cspell/dict-git@3.0.0':
     resolution: {integrity: sha512-simGS/lIiXbEaqJu9E2VPoYW1OTC2xrwPPXNXFMa2uo/50av56qOuaxDrZ5eH1LidFXwoc8HROCHYeKoNrDLSw==}
 
-  '@cspell/dict-golang@6.0.9':
-    resolution: {integrity: sha512-etDt2WQauyEQDA+qPS5QtkYTb2I9l5IfQftAllVoB1aOrT6bxxpHvMEpJ0Hsn/vezxrCqa/BmtUbRxllIxIuSg==}
+  '@cspell/dict-golang@6.0.11':
+    resolution: {integrity: sha512-BMFIDGh1HaFUe1cYBT1dotqyIQG2j3VkNntGQTBa/7i0aBnC5PBJDiAXnUeBHi0AVrz0hyAc7xtcK5KyKCEzwg==}
 
   '@cspell/dict-google@1.0.1':
     resolution: {integrity: sha512-dQr4M3n95uOhtloNSgB9tYYGXGGEGEykkFyRtfcp5pFuEecYUa0BSgtlGKx9RXVtJtKgR+yFT/a5uQSlt8WjqQ==}
@@ -384,8 +384,8 @@ packages:
   '@cspell/dict-powershell@5.0.5':
     resolution: {integrity: sha512-3JVyvMoDJesAATYGOxcUWPbQPUvpZmkinV3m8HL1w1RrjeMVXXuK7U1jhopSneBtLhkU+9HKFwgh9l9xL9mY2Q==}
 
-  '@cspell/dict-public-licenses@2.0.7':
-    resolution: {integrity: sha512-KlBXuGcN3LE7tQi/GEqKiDewWGGuopiAD0zRK1QilOx5Co8XAvs044gk4MNIQftc8r0nHeUI+irJKLGcR36DIQ==}
+  '@cspell/dict-public-licenses@2.0.8':
+    resolution: {integrity: sha512-Sup+tFS7cDV0fgpoKtUqEZ6+fA/H+XUgBiqQ/Fbs6vUE3WCjJHOIVsP+udHuyMH7iBfJ4UFYOYeORcY4EaKdMg==}
 
   '@cspell/dict-python@4.2.4':
     resolution: {integrity: sha512-sCtLBqMreb+8zRW2bXvFsfSnRUVU6IFm4mT6Dc4xbz0YajprbaPPh/kOUTw5IJRP8Uh+FFb7Xp2iH03CNWRq/A==}
@@ -402,8 +402,8 @@ packages:
   '@cspell/dict-scala@5.0.3':
     resolution: {integrity: sha512-4yGb4AInT99rqprxVNT9TYb1YSpq58Owzq7zi3ZS5T0u899Y4VsxsBiOgHnQ/4W+ygi+sp+oqef8w8nABR2lkg==}
 
-  '@cspell/dict-software-terms@4.0.6':
-    resolution: {integrity: sha512-UDhUzNSf7GN529a0Ip9hlSoGbpscz0YlUYBEJmZBXi8otpkrbCJqs50T74Ppd+SWqNil04De8urv4af2c6SY5Q==}
+  '@cspell/dict-software-terms@4.0.9':
+    resolution: {integrity: sha512-zh68RM83efPenrH0n/QLU5OwIg5fgJDxJiIo4ThQUgvaxihwd4R/iFVCDNJTdtjbn5eHqkjVXj8f5EKc3Y0OLA==}
 
   '@cspell/dict-sql@2.1.5':
     resolution: {integrity: sha512-FmxanytHXss7GAWAXmgaxl3icTCW7YxlimyOSPNfm+njqeUDjw3kEv4mFNDDObBJv8Ec5AWCbUDkWIpkE3IpKg==}
@@ -738,8 +738,8 @@ packages:
   '@types/minimatch@5.1.2':
     resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
 
-  '@types/node@22.4.1':
-    resolution: {integrity: sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==}
+  '@types/node@22.5.0':
+    resolution: {integrity: sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==}
 
   '@types/stack-utils@2.0.3':
     resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
@@ -1301,6 +1301,25 @@ packages:
     peerDependencies:
       eslint: '>=8.23.0'
 
+  eslint-plugin-perfectionist@3.2.0:
+    resolution: {integrity: sha512-cX1aztMbSfRWPKJH8CD+gadrbkS+RNH1OGWuNGws8J6rHzYYhawxWTU/yzMYjq2IRJCpBCfhgfa7BHRXQYxLHA==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+    peerDependencies:
+      astro-eslint-parser: ^1.0.2
+      eslint: '>=8.0.0'
+      svelte: '>=3.0.0'
+      svelte-eslint-parser: ^0.41.0
+      vue-eslint-parser: '>=9.0.0'
+    peerDependenciesMeta:
+      astro-eslint-parser:
+        optional: true
+      svelte:
+        optional: true
+      svelte-eslint-parser:
+        optional: true
+      vue-eslint-parser:
+        optional: true
+
   eslint-plugin-promise@7.1.0:
     resolution: {integrity: sha512-8trNmPxdAy3W620WKDpaS65NlM5yAumod6XeC4LOb+jxlkG4IVcp68c6dXY2ev+uT4U1PtG57YDV6EGAXN0GbQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1313,11 +1332,6 @@ packages:
     peerDependencies:
       eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
 
-  eslint-plugin-simple-import-sort@12.1.1:
-    resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==}
-    peerDependencies:
-      eslint: '>=5.0.0'
-
   eslint-scope@8.0.2:
     resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -1997,6 +2011,10 @@ packages:
     resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
     engines: {node: '>=18'}
 
+  minimatch@10.0.1:
+    resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==}
+    engines: {node: 20 || >=22}
+
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
 
@@ -2035,6 +2053,9 @@ packages:
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
+  natural-compare-lite@1.4.0:
+    resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
@@ -2401,8 +2422,8 @@ packages:
   spdx-expression-parse@4.0.0:
     resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==}
 
-  spdx-license-ids@3.0.18:
-    resolution: {integrity: sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==}
+  spdx-license-ids@3.0.20:
+    resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==}
 
   split2@4.2.0:
     resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
@@ -2753,11 +2774,11 @@ snapshots:
   '@biomejs/cli-win32-x64@1.8.3':
     optional: true
 
-  '@commitlint/cli@19.4.0(@types/node@22.4.1)(typescript@5.5.4)':
+  '@commitlint/cli@19.4.0(@types/node@22.5.0)(typescript@5.5.4)':
     dependencies:
       '@commitlint/format': 19.3.0
       '@commitlint/lint': 19.2.2
-      '@commitlint/load': 19.4.0(@types/node@22.4.1)(typescript@5.5.4)
+      '@commitlint/load': 19.4.0(@types/node@22.5.0)(typescript@5.5.4)
       '@commitlint/read': 19.4.0
       '@commitlint/types': 19.0.3
       execa: 8.0.1
@@ -2804,7 +2825,7 @@ snapshots:
       '@commitlint/rules': 19.0.3
       '@commitlint/types': 19.0.3
 
-  '@commitlint/load@19.4.0(@types/node@22.4.1)(typescript@5.5.4)':
+  '@commitlint/load@19.4.0(@types/node@22.5.0)(typescript@5.5.4)':
     dependencies:
       '@commitlint/config-validator': 19.0.3
       '@commitlint/execute-rule': 19.0.0
@@ -2812,7 +2833,7 @@ snapshots:
       '@commitlint/types': 19.0.3
       chalk: 5.3.0
       cosmiconfig: 9.0.0(typescript@5.5.4)
-      cosmiconfig-typescript-loader: 5.0.0(@types/node@22.4.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4)
+      cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.0)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
@@ -2870,14 +2891,14 @@ snapshots:
       '@cspell/dict-aws': 4.0.3
       '@cspell/dict-bash': 4.1.3
       '@cspell/dict-companies': 3.1.4
-      '@cspell/dict-cpp': 5.1.12
+      '@cspell/dict-cpp': 5.1.14
       '@cspell/dict-cryptocurrencies': 5.0.0
       '@cspell/dict-csharp': 4.0.2
       '@cspell/dict-css': 4.0.13
       '@cspell/dict-dart': 2.0.3
       '@cspell/dict-django': 4.1.0
       '@cspell/dict-docker': 1.1.7
-      '@cspell/dict-dotnet': 5.0.2
+      '@cspell/dict-dotnet': 5.0.3
       '@cspell/dict-elixir': 4.0.3
       '@cspell/dict-en-common-misspellings': 2.0.4
       '@cspell/dict-en-gb': 1.1.33
@@ -2888,7 +2909,7 @@ snapshots:
       '@cspell/dict-fullstack': 3.2.0
       '@cspell/dict-gaming-terms': 1.0.5
       '@cspell/dict-git': 3.0.0
-      '@cspell/dict-golang': 6.0.9
+      '@cspell/dict-golang': 6.0.11
       '@cspell/dict-google': 1.0.1
       '@cspell/dict-haskell': 4.0.1
       '@cspell/dict-html': 4.0.5
@@ -2905,13 +2926,13 @@ snapshots:
       '@cspell/dict-npm': 5.0.18
       '@cspell/dict-php': 4.0.8
       '@cspell/dict-powershell': 5.0.5
-      '@cspell/dict-public-licenses': 2.0.7
+      '@cspell/dict-public-licenses': 2.0.8
       '@cspell/dict-python': 4.2.4
       '@cspell/dict-r': 2.0.1
       '@cspell/dict-ruby': 5.0.2
       '@cspell/dict-rust': 4.0.5
       '@cspell/dict-scala': 5.0.3
-      '@cspell/dict-software-terms': 4.0.6
+      '@cspell/dict-software-terms': 4.0.9
       '@cspell/dict-sql': 2.1.5
       '@cspell/dict-svelte': 1.0.2
       '@cspell/dict-swift': 2.0.1
@@ -2937,7 +2958,7 @@ snapshots:
 
   '@cspell/dict-companies@3.1.4': {}
 
-  '@cspell/dict-cpp@5.1.12': {}
+  '@cspell/dict-cpp@5.1.14': {}
 
   '@cspell/dict-cryptocurrencies@5.0.0': {}
 
@@ -2953,7 +2974,7 @@ snapshots:
 
   '@cspell/dict-docker@1.1.7': {}
 
-  '@cspell/dict-dotnet@5.0.2': {}
+  '@cspell/dict-dotnet@5.0.3': {}
 
   '@cspell/dict-elixir@4.0.3': {}
 
@@ -2975,7 +2996,7 @@ snapshots:
 
   '@cspell/dict-git@3.0.0': {}
 
-  '@cspell/dict-golang@6.0.9': {}
+  '@cspell/dict-golang@6.0.11': {}
 
   '@cspell/dict-google@1.0.1': {}
 
@@ -3009,7 +3030,7 @@ snapshots:
 
   '@cspell/dict-powershell@5.0.5': {}
 
-  '@cspell/dict-public-licenses@2.0.7': {}
+  '@cspell/dict-public-licenses@2.0.8': {}
 
   '@cspell/dict-python@4.2.4':
     dependencies:
@@ -3023,7 +3044,7 @@ snapshots:
 
   '@cspell/dict-scala@5.0.3': {}
 
-  '@cspell/dict-software-terms@4.0.6': {}
+  '@cspell/dict-software-terms@4.0.9': {}
 
   '@cspell/dict-sql@2.1.5': {}
 
@@ -3124,7 +3145,7 @@ snapshots:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.6
       '@types/istanbul-reports': 3.0.4
-      '@types/node': 22.4.1
+      '@types/node': 22.5.0
       '@types/yargs': 17.0.33
       chalk: 4.1.2
 
@@ -3313,7 +3334,7 @@ snapshots:
 
   '@types/conventional-commits-parser@5.0.0':
     dependencies:
-      '@types/node': 22.4.1
+      '@types/node': 22.5.0
 
   '@types/eslint@9.6.0':
     dependencies:
@@ -3325,7 +3346,7 @@ snapshots:
   '@types/glob@7.2.0':
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 22.4.1
+      '@types/node': 22.5.0
 
   '@types/hast@3.0.4':
     dependencies:
@@ -3345,7 +3366,7 @@ snapshots:
 
   '@types/minimatch@5.1.2': {}
 
-  '@types/node@22.4.1':
+  '@types/node@22.5.0':
     dependencies:
       undici-types: 6.19.8
 
@@ -3723,9 +3744,9 @@ snapshots:
 
   core-util-is@1.0.3: {}
 
-  cosmiconfig-typescript-loader@5.0.0(@types/node@22.4.1)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4):
+  cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.0)(cosmiconfig@9.0.0(typescript@5.5.4))(typescript@5.5.4):
     dependencies:
-      '@types/node': 22.4.1
+      '@types/node': 22.5.0
       cosmiconfig: 9.0.0(typescript@5.5.4)
       jiti: 1.21.6
       typescript: 5.5.4
@@ -4053,6 +4074,17 @@ snapshots:
       minimatch: 9.0.5
       semver: 7.6.3
 
+  eslint-plugin-perfectionist@3.2.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4):
+    dependencies:
+      '@typescript-eslint/types': 8.2.0
+      '@typescript-eslint/utils': 8.2.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4)
+      eslint: 9.9.0(jiti@1.21.6)
+      minimatch: 10.0.1
+      natural-compare-lite: 1.4.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+
   eslint-plugin-promise@7.1.0(eslint@9.9.0(jiti@1.21.6)):
     dependencies:
       eslint: 9.9.0(jiti@1.21.6)
@@ -4079,10 +4111,6 @@ snapshots:
       string.prototype.matchall: 4.0.11
       string.prototype.repeat: 1.0.0
 
-  eslint-plugin-simple-import-sort@12.1.1(eslint@9.9.0(jiti@1.21.6)):
-    dependencies:
-      eslint: 9.9.0(jiti@1.21.6)
-
   eslint-scope@8.0.2:
     dependencies:
       esrecurse: 4.3.0
@@ -4609,7 +4637,7 @@ snapshots:
   jest-util@29.7.0:
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 22.4.1
+      '@types/node': 22.5.0
       chalk: 4.1.2
       ci-info: 3.9.0
       graceful-fs: 4.2.11
@@ -4786,6 +4814,10 @@ snapshots:
 
   mimic-function@5.0.1: {}
 
+  minimatch@10.0.1:
+    dependencies:
+      brace-expansion: 2.0.1
+
   minimatch@3.1.2:
     dependencies:
       brace-expansion: 1.1.11
@@ -4858,6 +4890,8 @@ snapshots:
 
   ms@2.1.3: {}
 
+  natural-compare-lite@1.4.0: {}
+
   natural-compare@1.4.0: {}
 
   neostandard@0.11.3(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4):
@@ -5245,9 +5279,9 @@ snapshots:
   spdx-expression-parse@4.0.0:
     dependencies:
       spdx-exceptions: 2.5.0
-      spdx-license-ids: 3.0.18
+      spdx-license-ids: 3.0.20
 
-  spdx-license-ids@3.0.18: {}
+  spdx-license-ids@3.0.20: {}
 
   split2@4.2.0: {}
 
index a6d7eae8e9b583565924217c1d953b2c861d27a7..826fba1eec4a2e1350b2cf8f68d5a514ea3f5687 100644 (file)
@@ -1,8 +1,7 @@
-import * as os from 'node:os'
-import { env } from 'node:process'
-
 import terser from '@rollup/plugin-terser'
 import typescript from '@rollup/plugin-typescript'
+import * as os from 'node:os'
+import { env } from 'node:process'
 import { defineConfig } from 'rollup'
 import analyze from 'rollup-plugin-analyzer'
 import command from 'rollup-plugin-command'
@@ -32,16 +31,16 @@ const maxWorkers = Math.floor(availableParallelism() / 2)
 
 export default defineConfig([
   {
+    external: [/^node:*/],
     input: './src/index.ts',
-    strictDeprecations: true,
     output: [
       {
         format: 'cjs',
         ...(isDevelopmentBuild
           ? {
+              chunkFileNames: '[name]-[hash].cjs',
               dir: './lib',
               entryFileNames: '[name].cjs',
-              chunkFileNames: '[name]-[hash].cjs',
               preserveModules: true,
               preserveModulesRoot: './src',
             }
@@ -57,9 +56,9 @@ export default defineConfig([
         format: 'esm',
         ...(isDevelopmentBuild
           ? {
+              chunkFileNames: '[name]-[hash].mjs',
               dir: './lib',
               entryFileNames: '[name].mjs',
-              chunkFileNames: '[name]-[hash].mjs',
               preserveModules: true,
               preserveModulesRoot: './src',
             }
@@ -72,13 +71,12 @@ export default defineConfig([
         }),
       },
     ],
-    external: [/^node:*/],
     plugins: [
       typescript({
-        tsconfig: './tsconfig.build.json',
         compilerOptions: {
           sourceMap: sourcemap,
         },
+        tsconfig: './tsconfig.build.json',
       }),
       del({
         targets: ['./lib/*'],
@@ -86,19 +84,20 @@ export default defineConfig([
       isAnalyzeBuild && analyze(),
       isDocumentationBuild && command('pnpm typedoc'),
     ],
+    strictDeprecations: true,
   },
   {
-    input: './lib/dts/index.d.ts',
-    strictDeprecations: true,
-    output: [{ format: 'esm', file: './lib/index.d.ts' }],
     external: [/^node:*/],
+    input: './lib/dts/index.d.ts',
+    output: [{ file: './lib/index.d.ts', format: 'esm' }],
     plugins: [
       dts(),
       del({
-        targets: ['./lib/dts'],
         hook: 'buildEnd',
+        targets: ['./lib/dts'],
       }),
       isAnalyzeBuild && analyze(),
     ],
+    strictDeprecations: true,
   },
 ])
index 5015483b290055e1a78e961c248024625800b6d6..803af1294f83eb0f094cd6d2ab40e52353408ed7 100644 (file)
@@ -8,10 +8,10 @@ export const defaultBufferSize = 2048
  * @internal
  */
 export class CircularBuffer {
-  private readIdx: number
-  private writeIdx: number
   private readonly items: Float32Array
   private readonly maxArrayIdx: number
+  private readIdx: number
+  private writeIdx: number
   public size: number
 
   /**
@@ -27,6 +27,23 @@ export class CircularBuffer {
     this.items = new Float32Array(size).fill(-1)
   }
 
+  /**
+   * Checks the buffer size.
+   * @param size - Buffer size.
+   */
+  private checkSize (size: number): void {
+    if (!Number.isSafeInteger(size)) {
+      throw new TypeError(
+        `Invalid circular buffer size: '${size.toString()}' is not an integer`
+      )
+    }
+    if (size < 0) {
+      throw new RangeError(
+        `Invalid circular buffer size: ${size.toString()} < 0`
+      )
+    }
+  }
+
   /**
    * Checks whether the buffer is empty.
    * @returns Whether the buffer is empty.
@@ -43,18 +60,6 @@ export class CircularBuffer {
     return this.size === this.items.length
   }
 
-  /**
-   * Puts number into buffer.
-   * @param number - Number to put into buffer.
-   */
-  public put (number: number): void {
-    this.items[this.writeIdx] = number
-    this.writeIdx = this.writeIdx === this.maxArrayIdx ? 0 : this.writeIdx + 1
-    if (this.size < this.items.length) {
-      ++this.size
-    }
-  }
-
   /**
    * Gets number from buffer.
    * @returns Number from buffer.
@@ -71,27 +76,22 @@ export class CircularBuffer {
   }
 
   /**
-   * Returns buffer as numbers' array.
-   * @returns Numbers' array.
+   * Puts number into buffer.
+   * @param number - Number to put into buffer.
    */
-  public toArray (): number[] {
-    return Array.from(this.items.filter(item => item !== -1))
+  public put (number: number): void {
+    this.items[this.writeIdx] = number
+    this.writeIdx = this.writeIdx === this.maxArrayIdx ? 0 : this.writeIdx + 1
+    if (this.size < this.items.length) {
+      ++this.size
+    }
   }
 
   /**
-   * Checks the buffer size.
-   * @param size - Buffer size.
+   * Returns buffer as numbers' array.
+   * @returns Numbers' array.
    */
-  private checkSize (size: number): void {
-    if (!Number.isSafeInteger(size)) {
-      throw new TypeError(
-        `Invalid circular buffer size: '${size.toString()}' is not an integer`
-      )
-    }
-    if (size < 0) {
-      throw new RangeError(
-        `Invalid circular buffer size: ${size.toString()} < 0`
-      )
-    }
+  public toArray (): number[] {
+    return Array.from(this.items.filter(item => item !== -1))
   }
 }
index cd55b7ff0b7096ba4d2ea5db872e4c44940809a5..f04f03f89634139f4deef78a81b507dfb2ab3219 100644 (file)
@@ -1,16 +1,29 @@
+import type { TransferListItem } from 'node:worker_threads'
+
 import { AsyncResource } from 'node:async_hooks'
 import { randomUUID } from 'node:crypto'
 import { EventEmitterAsyncResource } from 'node:events'
 import { performance } from 'node:perf_hooks'
-import type { TransferListItem } from 'node:worker_threads'
 
-import { defaultBucketSize } from '../queues/queue-types.js'
 import type {
   MessageValue,
   PromiseResponseWrapper,
   Task,
   TaskFunctionProperties,
 } from '../utility-types.js'
+import type {
+  TaskFunction,
+  TaskFunctionObject,
+} from '../worker/task-functions.js'
+import type {
+  IWorker,
+  IWorkerNode,
+  WorkerInfo,
+  WorkerNodeEventDetail,
+  WorkerType,
+} from './worker.js'
+
+import { defaultBucketSize } from '../queues/queue-types.js'
 import {
   average,
   buildTaskFunctionProperties,
@@ -25,10 +38,6 @@ import {
   round,
   sleep,
 } from '../utils.js'
-import type {
-  TaskFunction,
-  TaskFunctionObject,
-} from '../worker/task-functions.js'
 import { KillBehaviors } from '../worker/worker-options.js'
 import {
   type IPool,
@@ -59,13 +68,6 @@ import {
   waitWorkerNodeEvents,
 } from './utils.js'
 import { version } from './version.js'
-import type {
-  IWorker,
-  IWorkerNode,
-  WorkerInfo,
-  WorkerNodeEventDetail,
-  WorkerType,
-} from './worker.js'
 import { WorkerNode } from './worker-node.js'
 
 /**
@@ -79,12 +81,6 @@ export abstract class AbstractPool<
   Data = unknown,
   Response = unknown
 > implements IPool<Worker, Data, Response> {
-  /** @inheritDoc */
-  public readonly workerNodes: IWorkerNode<Worker, Data>[] = []
-
-  /** @inheritDoc */
-  public emitter?: EventEmitterAsyncResource
-
   /**
    * The task execution response promise map:
    * - `key`: The message id of each submitted task.
@@ -110,1272 +106,649 @@ export abstract class AbstractPool<
   >
 
   /**
-   * The task functions added at runtime map:
-   * - `key`: The task function name.
-   * - `value`: The task function object.
+   * This method is the message listener registered on each worker.
+   * @param message - The message received from the worker.
    */
-  private readonly taskFunctions: Map<
-    string,
-    TaskFunctionObject<Data, Response>
-  >
+  protected readonly workerMessageListener = (
+    message: MessageValue<Response>
+  ): void => {
+    this.checkMessageWorkerId(message)
+    const { ready, taskFunctionsProperties, taskId, workerId } = message
+    if (ready != null && taskFunctionsProperties != null) {
+      // Worker ready response received from worker
+      this.handleWorkerReadyResponse(message)
+    } else if (taskFunctionsProperties != null) {
+      // Task function properties message received from worker
+      const workerNodeKey = this.getWorkerNodeKeyByWorkerId(workerId)
+      const workerInfo = this.getWorkerInfo(workerNodeKey)
+      if (workerInfo != null) {
+        workerInfo.taskFunctionsProperties = taskFunctionsProperties
+        this.sendStatisticsMessageToWorker(workerNodeKey)
+        this.setTasksQueuePriority(workerNodeKey)
+      }
+    } else if (taskId != null) {
+      // Task execution response received from worker
+      this.handleTaskExecutionResponse(message)
+    }
+  }
 
-  /**
-   * Whether the pool is started or not.
-   */
-  private started: boolean
-  /**
-   * Whether the pool is starting or not.
-   */
-  private starting: boolean
   /**
    * Whether the pool is destroying or not.
    */
   private destroying: boolean
+
   /**
-   * Whether the minimum number of workers is starting or not.
-   */
-  private startingMinimumNumberOfWorkers: boolean
-  /**
-   * Whether the pool ready event has been emitted or not.
+   * Gets task function worker choice 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 readyEventEmitted: boolean
+  private readonly getTaskFunctionWorkerChoiceStrategy = (
+    name?: string
+  ): undefined | WorkerChoiceStrategy => {
+    name = name ?? DEFAULT_TASK_NAME
+    const taskFunctionsProperties = this.listTaskFunctionsProperties()
+    if (name === DEFAULT_TASK_NAME) {
+      name = taskFunctionsProperties[1]?.name
+    }
+    return taskFunctionsProperties.find(
+      (taskFunctionProperties: TaskFunctionProperties) =>
+        taskFunctionProperties.name === name
+    )?.strategy
+  }
+
   /**
-   * The start timestamp of the pool.
+   * Gets the worker choice strategies registered in this pool.
+   * @returns The worker choice strategies.
    */
-  private startTimestamp?: number
+  private readonly getWorkerChoiceStrategies =
+    (): 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: undefined | WorkerChoiceStrategy) => strategy != null
+          ),
+      ])
+    }
 
   /**
-   * Constructs a new poolifier pool.
-   * @param minimumNumberOfWorkers - Minimum number of workers that this pool manages.
-   * @param filePath - Path to the worker file.
-   * @param opts - Options for the pool.
-   * @param maximumNumberOfWorkers - Maximum number of workers that this pool manages.
+   * Gets worker node task function priority, if any.
+   * @param workerNodeKey - The worker node key.
+   * @param name - The task function name.
+   * @returns The worker node task function priority if the worker node task function priority is defined, `undefined` otherwise.
    */
-  public constructor (
-    protected readonly minimumNumberOfWorkers: number,
-    protected readonly filePath: string,
-    protected readonly opts: PoolOptions<Worker>,
-    protected readonly maximumNumberOfWorkers?: number
-  ) {
-    if (!this.isMain()) {
-      throw new Error(
-        'Cannot start a pool from a worker with the same type as the pool'
-      )
+  private readonly getWorkerNodeTaskFunctionPriority = (
+    workerNodeKey: number,
+    name?: string
+  ): number | undefined => {
+    const workerInfo = this.getWorkerInfo(workerNodeKey)
+    if (workerInfo == null) {
+      return
     }
-    this.checkPoolType()
-    checkFilePath(this.filePath)
-    this.checkMinimumNumberOfWorkers(this.minimumNumberOfWorkers)
-    this.checkPoolOptions(this.opts)
-
-    this.chooseWorkerNode = this.chooseWorkerNode.bind(this)
-    this.executeTask = this.executeTask.bind(this)
-    this.enqueueTask = this.enqueueTask.bind(this)
-
-    if (this.opts.enableEvents === true) {
-      this.initEventEmitter()
+    name = name ?? DEFAULT_TASK_NAME
+    if (name === DEFAULT_TASK_NAME) {
+      name = workerInfo.taskFunctionsProperties?.[1]?.name
     }
-    this.workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext<
-      Worker,
-      Data,
-      Response
-    >(
-      this,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      [this.opts.workerChoiceStrategy!],
-      this.opts.workerChoiceStrategyOptions
-    )
-
-    this.setupHook()
-
-    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
+    return workerInfo.taskFunctionsProperties?.find(
+      (taskFunctionProperties: TaskFunctionProperties) =>
+        taskFunctionProperties.name === name
+    )?.priority
+  }
 
-    this.started = false
-    this.starting = false
-    this.destroying = false
-    this.readyEventEmitted = false
-    this.startingMinimumNumberOfWorkers = false
-    if (this.opts.startWorkers === true) {
-      this.start()
+  /**
+   * Gets worker node task function worker choice strategy, if any.
+   * @param workerNodeKey - The worker node key.
+   * @param name - The task function name.
+   * @returns The worker node task function worker choice strategy if the worker node task function worker choice strategy is defined, `undefined` otherwise.
+   */
+  private readonly getWorkerNodeTaskFunctionWorkerChoiceStrategy = (
+    workerNodeKey: number,
+    name?: string
+  ): undefined | WorkerChoiceStrategy => {
+    const workerInfo = this.getWorkerInfo(workerNodeKey)
+    if (workerInfo == null) {
+      return
+    }
+    name = name ?? DEFAULT_TASK_NAME
+    if (name === DEFAULT_TASK_NAME) {
+      name = workerInfo.taskFunctionsProperties?.[1]?.name
     }
+    return workerInfo.taskFunctionsProperties?.find(
+      (taskFunctionProperties: TaskFunctionProperties) =>
+        taskFunctionProperties.name === name
+    )?.strategy
   }
 
-  private checkPoolType (): void {
-    if (this.type === PoolTypes.fixed && this.maximumNumberOfWorkers != null) {
-      throw new Error(
-        'Cannot instantiate a fixed pool with a maximum number of workers specified at initialization'
+  private readonly handleWorkerNodeBackPressureEvent = (
+    eventDetail: WorkerNodeEventDetail
+  ): void => {
+    if (
+      this.cannotStealTask() ||
+      this.hasBackPressure() ||
+      (this.info.stealingWorkerNodes ?? 0) >
+        Math.round(
+          this.workerNodes.length *
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            this.opts.tasksQueueOptions!.tasksStealingRatio!
+        )
+    ) {
+      return
+    }
+    const sizeOffset = 1
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    if (this.opts.tasksQueueOptions!.size! <= sizeOffset) {
+      return
+    }
+    const { workerId } = eventDetail
+    const sourceWorkerNode =
+      this.workerNodes[this.getWorkerNodeKeyByWorkerId(workerId)]
+    const workerNodes = this.workerNodes
+      .slice()
+      .sort(
+        (workerNodeA, workerNodeB) =>
+          workerNodeA.usage.tasks.queued - workerNodeB.usage.tasks.queued
       )
+    for (const [workerNodeKey, workerNode] of workerNodes.entries()) {
+      if (
+        sourceWorkerNode.usage.tasks.queued > 0 &&
+        workerNode.info.id !== workerId &&
+        workerNode.usage.tasks.queued <
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.opts.tasksQueueOptions!.size! - sizeOffset
+      ) {
+        if (workerNode.info.backPressureStealing) {
+          continue
+        }
+        workerNode.info.backPressureStealing = true
+        this.stealTask(sourceWorkerNode, workerNodeKey)
+        workerNode.info.backPressureStealing = false
+      }
     }
   }
 
-  private checkMinimumNumberOfWorkers (
-    minimumNumberOfWorkers: number | undefined
-  ): void {
-    if (minimumNumberOfWorkers == null) {
+  private readonly handleWorkerNodeIdleEvent = (
+    eventDetail: WorkerNodeEventDetail,
+    previousStolenTask?: Task<Data>
+  ): void => {
+    const { workerNodeKey } = eventDetail
+    if (workerNodeKey == null) {
       throw new Error(
-        'Cannot instantiate a pool without specifying the number of workers'
-      )
-    } else if (!Number.isSafeInteger(minimumNumberOfWorkers)) {
-      throw new TypeError(
-        'Cannot instantiate a pool with a non safe integer number of workers'
-      )
-    } else if (minimumNumberOfWorkers < 0) {
-      throw new RangeError(
-        'Cannot instantiate a pool with a negative number of workers'
+        "WorkerNode event detail 'workerNodeKey' property must be defined"
       )
-    } else if (this.type === PoolTypes.fixed && minimumNumberOfWorkers === 0) {
-      throw new RangeError('Cannot instantiate a fixed pool with zero worker')
     }
-  }
-
-  private checkPoolOptions (opts: PoolOptions<Worker>): void {
-    if (isPlainObject(opts)) {
-      this.opts.startWorkers = opts.startWorkers ?? true
-      checkValidWorkerChoiceStrategy(opts.workerChoiceStrategy)
-      this.opts.workerChoiceStrategy =
-        opts.workerChoiceStrategy ?? WorkerChoiceStrategies.ROUND_ROBIN
-      this.checkValidWorkerChoiceStrategyOptions(
-        opts.workerChoiceStrategyOptions
+    const workerNodeInfo = this.getWorkerInfo(workerNodeKey)
+    if (workerNodeInfo == null) {
+      throw new Error(
+        `Worker node with key '${workerNodeKey.toString()}' not found in pool`
       )
-      if (opts.workerChoiceStrategyOptions != null) {
-        this.opts.workerChoiceStrategyOptions = opts.workerChoiceStrategyOptions
-      }
-      this.opts.restartWorkerOnError = opts.restartWorkerOnError ?? true
-      this.opts.enableEvents = opts.enableEvents ?? true
-      this.opts.enableTasksQueue = opts.enableTasksQueue ?? false
-      if (this.opts.enableTasksQueue) {
-        checkValidTasksQueueOptions(opts.tasksQueueOptions)
-        this.opts.tasksQueueOptions = this.buildTasksQueueOptions(
-          opts.tasksQueueOptions
-        )
-      }
-    } else {
-      throw new TypeError('Invalid pool options: must be a plain object')
     }
-  }
-
-  private checkValidWorkerChoiceStrategyOptions (
-    workerChoiceStrategyOptions: WorkerChoiceStrategyOptions | undefined
-  ): void {
     if (
-      workerChoiceStrategyOptions != null &&
-      !isPlainObject(workerChoiceStrategyOptions)
+      !workerNodeInfo.continuousStealing &&
+      (this.cannotStealTask() ||
+        (this.info.stealingWorkerNodes ?? 0) >
+          Math.round(
+            this.workerNodes.length *
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              this.opts.tasksQueueOptions!.tasksStealingRatio!
+          ))
     ) {
-      throw new TypeError(
-        'Invalid worker choice strategy options: must be a plain object'
-      )
+      return
     }
+    const workerNodeTasksUsage = this.workerNodes[workerNodeKey].usage.tasks
     if (
-      workerChoiceStrategyOptions?.weights != null &&
-      Object.keys(workerChoiceStrategyOptions.weights).length !==
-        (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
+      workerNodeInfo.continuousStealing &&
+      (workerNodeTasksUsage.executing > 0 ||
+        this.tasksQueueSize(workerNodeKey) > 0)
     ) {
+      workerNodeInfo.continuousStealing = false
+      if (workerNodeTasksUsage.sequentiallyStolen > 0) {
+        this.resetTaskSequentiallyStolenStatisticsWorkerUsage(
+          workerNodeKey,
+          previousStolenTask?.name
+        )
+      }
+      return
+    }
+    workerNodeInfo.continuousStealing = true
+    const stolenTask = this.workerNodeStealTask(workerNodeKey)
+    this.updateTaskSequentiallyStolenStatisticsWorkerUsage(
+      workerNodeKey,
+      stolenTask?.name,
+      previousStolenTask?.name
+    )
+    sleep(exponentialDelay(workerNodeTasksUsage.sequentiallyStolen))
+      .then(() => {
+        this.handleWorkerNodeIdleEvent(eventDetail, stolenTask)
+        return undefined
+      })
+      .catch((error: unknown) => {
+        this.emitter?.emit(PoolEvents.error, error)
+      })
+  }
+
+  /**
+   * Whether the pool ready event has been emitted or not.
+   */
+  private readyEventEmitted: boolean
+
+  /**
+   * Whether the pool is started or not.
+   */
+  private started: boolean
+
+  /**
+   * Whether the pool is starting or not.
+   */
+  private starting: boolean
+
+  /**
+   * Whether the minimum number of workers is starting or not.
+   */
+  private startingMinimumNumberOfWorkers: boolean
+
+  /**
+   * The start timestamp of the pool.
+   */
+  private startTimestamp?: number
+
+  private readonly stealTask = (
+    sourceWorkerNode: IWorkerNode<Worker, Data>,
+    destinationWorkerNodeKey: number
+  ): Task<Data> | undefined => {
+    const destinationWorkerInfo = this.getWorkerInfo(destinationWorkerNodeKey)
+    if (destinationWorkerInfo == null) {
       throw new Error(
-        'Invalid worker choice strategy options: must have a weight for each worker node'
+        `Worker node with key '${destinationWorkerNodeKey.toString()}' not found in pool`
       )
     }
+    // Avoid cross and cascading task stealing. Could be smarter by checking stealing/stolen worker ids pair.
     if (
-      workerChoiceStrategyOptions?.measurement != null &&
-      !Object.values(Measurements).includes(
-        workerChoiceStrategyOptions.measurement
-      )
+      !sourceWorkerNode.info.ready ||
+      sourceWorkerNode.info.stolen ||
+      sourceWorkerNode.info.stealing ||
+      !destinationWorkerInfo.ready ||
+      destinationWorkerInfo.stolen ||
+      destinationWorkerInfo.stealing
     ) {
-      throw new Error(
-        `Invalid worker choice strategy options: invalid measurement '${workerChoiceStrategyOptions.measurement}'`
-      )
+      return
     }
+    destinationWorkerInfo.stealing = true
+    sourceWorkerNode.info.stolen = true
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const stolenTask = sourceWorkerNode.dequeueLastPrioritizedTask()!
+    sourceWorkerNode.info.stolen = false
+    destinationWorkerInfo.stealing = false
+    this.handleTask(destinationWorkerNodeKey, stolenTask)
+    this.updateTaskStolenStatisticsWorkerUsage(
+      destinationWorkerNodeKey,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      stolenTask.name!
+    )
+    return stolenTask
   }
 
-  private initEventEmitter (): void {
-    this.emitter = new EventEmitterAsyncResource({
-      name: `poolifier:${this.type}-${this.worker}-pool`,
-    })
+  /**
+   * The task functions added at runtime map:
+   * - `key`: The task function name.
+   * - `value`: The task function object.
+   */
+  private readonly taskFunctions: Map<
+    string,
+    TaskFunctionObject<Data, Response>
+  >
+
+  private readonly workerNodeStealTask = (
+    workerNodeKey: number
+  ): Task<Data> | undefined => {
+    const workerNodes = this.workerNodes
+      .slice()
+      .sort(
+        (workerNodeA, workerNodeB) =>
+          workerNodeB.usage.tasks.queued - workerNodeA.usage.tasks.queued
+      )
+    const sourceWorkerNode = workerNodes.find(
+      (sourceWorkerNode, sourceWorkerNodeKey) =>
+        sourceWorkerNodeKey !== workerNodeKey &&
+        sourceWorkerNode.usage.tasks.queued > 0
+    )
+    if (sourceWorkerNode != null) {
+      return this.stealTask(sourceWorkerNode, workerNodeKey)
+    }
   }
 
   /** @inheritDoc */
-  public get info (): PoolInfo {
-    return {
-      version,
-      type: this.type,
-      worker: this.worker,
-      started: this.started,
-      ready: this.ready,
+  public emitter?: EventEmitterAsyncResource
+
+  /** @inheritDoc */
+  public readonly workerNodes: IWorkerNode<Worker, Data>[] = []
+
+  /**
+   * Constructs a new poolifier pool.
+   * @param minimumNumberOfWorkers - Minimum number of workers that this pool manages.
+   * @param filePath - Path to the worker file.
+   * @param opts - Options for the pool.
+   * @param maximumNumberOfWorkers - Maximum number of workers that this pool manages.
+   */
+  public constructor (
+    protected readonly minimumNumberOfWorkers: number,
+    protected readonly filePath: string,
+    protected readonly opts: PoolOptions<Worker>,
+    protected readonly maximumNumberOfWorkers?: number
+  ) {
+    if (!this.isMain()) {
+      throw new Error(
+        'Cannot start a pool from a worker with the same type as the pool'
+      )
+    }
+    this.checkPoolType()
+    checkFilePath(this.filePath)
+    this.checkMinimumNumberOfWorkers(this.minimumNumberOfWorkers)
+    this.checkPoolOptions(this.opts)
+
+    this.chooseWorkerNode = this.chooseWorkerNode.bind(this)
+    this.executeTask = this.executeTask.bind(this)
+    this.enqueueTask = this.enqueueTask.bind(this)
+
+    if (this.opts.enableEvents === true) {
+      this.initEventEmitter()
+    }
+    this.workerChoiceStrategiesContext = new WorkerChoiceStrategiesContext<
+      Worker,
+      Data,
+      Response
+    >(
+      this,
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      defaultStrategy: this.opts.workerChoiceStrategy!,
-      strategyRetries: this.workerChoiceStrategiesContext?.retriesCount ?? 0,
-      minSize: this.minimumNumberOfWorkers,
-      maxSize: this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers,
-      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-        .runTime.aggregate === true &&
-        this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-          .waitTime.aggregate && {
-        utilization: round(this.utilization),
-      }),
-      workerNodes: this.workerNodes.length,
-      idleWorkerNodes: this.workerNodes.reduce(
-        (accumulator, _, workerNodeKey) =>
-          this.isWorkerNodeIdle(workerNodeKey) ? accumulator + 1 : accumulator,
-        0
-      ),
-      busyWorkerNodes: this.workerNodes.reduce(
-        (accumulator, _, workerNodeKey) =>
-          this.isWorkerNodeBusy(workerNodeKey) ? accumulator + 1 : accumulator,
-        0
-      ),
-      ...(this.opts.enableTasksQueue === true && {
-        stealingWorkerNodes: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            workerNode.info.continuousStealing ||
-            workerNode.info.backPressureStealing
-              ? accumulator + 1
-              : accumulator,
-          0
-        ),
-      }),
-      ...(this.opts.enableTasksQueue === true && {
-        backPressureWorkerNodes: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            workerNode.info.backPressure ? accumulator + 1 : accumulator,
-          0
-        ),
-      }),
-      executedTasks: this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          accumulator + workerNode.usage.tasks.executed,
-        0
-      ),
-      executingTasks: this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          accumulator + workerNode.usage.tasks.executing,
-        0
-      ),
-      ...(this.opts.enableTasksQueue === true && {
-        queuedTasks: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            accumulator + workerNode.usage.tasks.queued,
-          0
-        ),
-      }),
-      ...(this.opts.enableTasksQueue === true && {
-        maxQueuedTasks: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            accumulator + (workerNode.usage.tasks.maxQueued ?? 0),
-          0
-        ),
-      }),
-      ...(this.opts.enableTasksQueue === true && {
-        backPressure: this.hasBackPressure(),
-      }),
-      ...(this.opts.enableTasksQueue === true && {
-        stolenTasks: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            accumulator + workerNode.usage.tasks.stolen,
-          0
-        ),
-      }),
-      failedTasks: this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          accumulator + workerNode.usage.tasks.failed,
-        0
-      ),
-      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-        .runTime.aggregate === true && {
-        runTime: {
-          minimum: round(
-            min(
-              ...this.workerNodes.map(
-                workerNode =>
-                  workerNode.usage.runTime.minimum ?? Number.POSITIVE_INFINITY
-              )
-            )
-          ),
-          maximum: round(
-            max(
-              ...this.workerNodes.map(
-                workerNode =>
-                  workerNode.usage.runTime.maximum ?? Number.NEGATIVE_INFINITY
-              )
-            )
-          ),
-          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-            .runTime.average && {
-            average: round(
-              average(
-                this.workerNodes.reduce<number[]>(
-                  (accumulator, workerNode) =>
-                    accumulator.concat(
-                      workerNode.usage.runTime.history.toArray()
-                    ),
-                  []
-                )
-              )
-            ),
-          }),
-          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-            .runTime.median && {
-            median: round(
-              median(
-                this.workerNodes.reduce<number[]>(
-                  (accumulator, workerNode) =>
-                    accumulator.concat(
-                      workerNode.usage.runTime.history.toArray()
-                    ),
-                  []
-                )
-              )
-            ),
-          }),
-        },
-      }),
-      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-        .waitTime.aggregate === true && {
-        waitTime: {
-          minimum: round(
-            min(
-              ...this.workerNodes.map(
-                workerNode =>
-                  workerNode.usage.waitTime.minimum ?? Number.POSITIVE_INFINITY
-              )
-            )
-          ),
-          maximum: round(
-            max(
-              ...this.workerNodes.map(
-                workerNode =>
-                  workerNode.usage.waitTime.maximum ?? Number.NEGATIVE_INFINITY
-              )
-            )
-          ),
-          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-            .waitTime.average && {
-            average: round(
-              average(
-                this.workerNodes.reduce<number[]>(
-                  (accumulator, workerNode) =>
-                    accumulator.concat(
-                      workerNode.usage.waitTime.history.toArray()
-                    ),
-                  []
-                )
-              )
-            ),
-          }),
-          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-            .waitTime.median && {
-            median: round(
-              median(
-                this.workerNodes.reduce<number[]>(
-                  (accumulator, workerNode) =>
-                    accumulator.concat(
-                      workerNode.usage.waitTime.history.toArray()
-                    ),
-                  []
-                )
-              )
-            ),
-          }),
-        },
-      }),
-      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-        .elu.aggregate === true && {
-        elu: {
-          idle: {
-            minimum: round(
-              min(
-                ...this.workerNodes.map(
-                  workerNode =>
-                    workerNode.usage.elu.idle.minimum ??
-                    Number.POSITIVE_INFINITY
-                )
-              )
-            ),
-            maximum: round(
-              max(
-                ...this.workerNodes.map(
-                  workerNode =>
-                    workerNode.usage.elu.idle.maximum ??
-                    Number.NEGATIVE_INFINITY
-                )
-              )
-            ),
-            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-              .elu.average && {
-              average: round(
-                average(
-                  this.workerNodes.reduce<number[]>(
-                    (accumulator, workerNode) =>
-                      accumulator.concat(
-                        workerNode.usage.elu.idle.history.toArray()
-                      ),
-                    []
-                  )
-                )
-              ),
-            }),
-            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-              .elu.median && {
-              median: round(
-                median(
-                  this.workerNodes.reduce<number[]>(
-                    (accumulator, workerNode) =>
-                      accumulator.concat(
-                        workerNode.usage.elu.idle.history.toArray()
-                      ),
-                    []
-                  )
-                )
-              ),
-            }),
-          },
-          active: {
-            minimum: round(
-              min(
-                ...this.workerNodes.map(
-                  workerNode =>
-                    workerNode.usage.elu.active.minimum ??
-                    Number.POSITIVE_INFINITY
-                )
-              )
-            ),
-            maximum: round(
-              max(
-                ...this.workerNodes.map(
-                  workerNode =>
-                    workerNode.usage.elu.active.maximum ??
-                    Number.NEGATIVE_INFINITY
-                )
-              )
-            ),
-            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-              .elu.average && {
-              average: round(
-                average(
-                  this.workerNodes.reduce<number[]>(
-                    (accumulator, workerNode) =>
-                      accumulator.concat(
-                        workerNode.usage.elu.active.history.toArray()
-                      ),
-                    []
-                  )
-                )
-              ),
-            }),
-            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
-              .elu.median && {
-              median: round(
-                median(
-                  this.workerNodes.reduce<number[]>(
-                    (accumulator, workerNode) =>
-                      accumulator.concat(
-                        workerNode.usage.elu.active.history.toArray()
-                      ),
-                    []
-                  )
-                )
-              ),
-            }),
-          },
-          utilization: {
-            average: round(
-              average(
-                this.workerNodes.map(
-                  workerNode => workerNode.usage.elu.utilization ?? 0
-                )
-              )
-            ),
-            median: round(
-              median(
-                this.workerNodes.map(
-                  workerNode => workerNode.usage.elu.utilization ?? 0
-                )
-              )
-            ),
-          },
-        },
-      }),
-    }
-  }
-
-  /**
-   * Whether the pool is ready or not.
-   * @returns The pool readiness boolean status.
-   */
-  private get ready (): boolean {
-    if (this.empty) {
-      return false
-    }
-    return (
-      this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          !workerNode.info.dynamic && workerNode.info.ready
-            ? accumulator + 1
-            : accumulator,
-        0
-      ) >= this.minimumNumberOfWorkers
-    )
-  }
-
-  /**
-   * Whether the pool is empty or not.
-   * @returns The pool emptiness boolean status.
-   */
-  protected get empty (): boolean {
-    return this.minimumNumberOfWorkers === 0 && this.workerNodes.length === 0
-  }
-
-  /**
-   * The approximate pool utilization.
-   * @returns The pool utilization.
-   */
-  private get utilization (): number {
-    if (this.startTimestamp == null) {
-      return 0
-    }
-    const poolTimeCapacity =
-      (performance.now() - this.startTimestamp) *
-      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
-    const totalTasksRunTime = this.workerNodes.reduce(
-      (accumulator, workerNode) =>
-        accumulator + (workerNode.usage.runTime.aggregate ?? 0),
-      0
-    )
-    const totalTasksWaitTime = this.workerNodes.reduce(
-      (accumulator, workerNode) =>
-        accumulator + (workerNode.usage.waitTime.aggregate ?? 0),
-      0
-    )
-    return (totalTasksRunTime + totalTasksWaitTime) / poolTimeCapacity
-  }
-
-  /**
-   * The pool type.
-   *
-   * If it is `'dynamic'`, it provides the `max` property.
-   */
-  protected abstract get type (): PoolType
-
-  /**
-   * The worker type.
-   */
-  protected abstract get worker (): WorkerType
-
-  /**
-   * Checks if the worker id sent in the received message from a worker is valid.
-   * @param message - The received message.
-   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the worker id is invalid.
-   */
-  private checkMessageWorkerId (message: MessageValue<Data | Response>): void {
-    if (message.workerId == null) {
-      throw new Error('Worker message received without worker id')
-    } else if (this.getWorkerNodeKeyByWorkerId(message.workerId) === -1) {
-      throw new Error(
-        `Worker message received from unknown worker '${message.workerId.toString()}'`
-      )
-    }
-  }
-
-  /**
-   * Gets the worker node key given its worker id.
-   * @param workerId - The worker id.
-   * @returns The worker node key if the worker id is found in the pool worker nodes, `-1` otherwise.
-   */
-  private getWorkerNodeKeyByWorkerId (workerId: number | undefined): number {
-    return this.workerNodes.findIndex(
-      workerNode => workerNode.info.id === workerId
-    )
-  }
-
-  /** @inheritDoc */
-  public setWorkerChoiceStrategy (
-    workerChoiceStrategy: WorkerChoiceStrategy,
-    workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions
-  ): void {
-    let requireSync = false
-    checkValidWorkerChoiceStrategy(workerChoiceStrategy)
-    if (workerChoiceStrategyOptions != null) {
-      requireSync = !this.setWorkerChoiceStrategyOptions(
-        workerChoiceStrategyOptions
-      )
-    }
-    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.getWorkerChoiceStrategies(),
-        this.opts.workerChoiceStrategyOptions
-      )
-      for (const workerNodeKey of this.workerNodes.keys()) {
-        this.sendStatisticsMessageToWorker(workerNodeKey)
-      }
-    }
-  }
-
-  /** @inheritDoc */
-  public setWorkerChoiceStrategyOptions (
-    workerChoiceStrategyOptions: WorkerChoiceStrategyOptions | undefined
-  ): boolean {
-    this.checkValidWorkerChoiceStrategyOptions(workerChoiceStrategyOptions)
-    if (workerChoiceStrategyOptions != null) {
-      this.opts.workerChoiceStrategyOptions = {
-        ...this.opts.workerChoiceStrategyOptions,
-        ...workerChoiceStrategyOptions,
-      }
-      this.workerChoiceStrategiesContext?.setOptions(
-        this.opts.workerChoiceStrategyOptions
-      )
-      this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
-        this.getWorkerChoiceStrategies(),
-        this.opts.workerChoiceStrategyOptions
-      )
-      for (const workerNodeKey of this.workerNodes.keys()) {
-        this.sendStatisticsMessageToWorker(workerNodeKey)
-      }
-      return true
-    }
-    return false
-  }
-
-  /** @inheritDoc */
-  public enableTasksQueue (
-    enable: boolean,
-    tasksQueueOptions?: TasksQueueOptions
-  ): void {
-    if (this.opts.enableTasksQueue === true && !enable) {
-      this.unsetTaskStealing()
-      this.unsetTasksStealingOnBackPressure()
-      this.flushTasksQueues()
-    }
-    this.opts.enableTasksQueue = enable
-    this.setTasksQueueOptions(tasksQueueOptions)
-  }
-
-  /** @inheritDoc */
-  public setTasksQueueOptions (
-    tasksQueueOptions: TasksQueueOptions | undefined
-  ): void {
-    if (this.opts.enableTasksQueue === true) {
-      checkValidTasksQueueOptions(tasksQueueOptions)
-      this.opts.tasksQueueOptions =
-        this.buildTasksQueueOptions(tasksQueueOptions)
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.setTasksQueueSize(this.opts.tasksQueueOptions.size!)
-      if (this.opts.tasksQueueOptions.taskStealing === true) {
-        this.unsetTaskStealing()
-        this.setTaskStealing()
-      } else {
-        this.unsetTaskStealing()
-      }
-      if (this.opts.tasksQueueOptions.tasksStealingOnBackPressure === true) {
-        this.unsetTasksStealingOnBackPressure()
-        this.setTasksStealingOnBackPressure()
-      } else {
-        this.unsetTasksStealingOnBackPressure()
-      }
-    } else if (this.opts.tasksQueueOptions != null) {
-      delete this.opts.tasksQueueOptions
-    }
-  }
-
-  private buildTasksQueueOptions (
-    tasksQueueOptions: TasksQueueOptions | undefined
-  ): TasksQueueOptions {
-    return {
-      ...getDefaultTasksQueueOptions(
-        this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
-      ),
-      ...this.opts.tasksQueueOptions,
-      ...tasksQueueOptions,
-    }
-  }
-
-  private setTasksQueueSize (size: number): void {
-    for (const workerNode of this.workerNodes) {
-      workerNode.tasksQueueBackPressureSize = size
-    }
-  }
-
-  private setTaskStealing (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.workerNodes[workerNodeKey].on('idle', this.handleWorkerNodeIdleEvent)
-    }
-  }
-
-  private unsetTaskStealing (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.workerNodes[workerNodeKey].off(
-        'idle',
-        this.handleWorkerNodeIdleEvent
-      )
-    }
-  }
-
-  private setTasksStealingOnBackPressure (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.workerNodes[workerNodeKey].on(
-        'backPressure',
-        this.handleWorkerNodeBackPressureEvent
-      )
-    }
-  }
-
-  private unsetTasksStealingOnBackPressure (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.workerNodes[workerNodeKey].off(
-        'backPressure',
-        this.handleWorkerNodeBackPressureEvent
-      )
-    }
-  }
-
-  /**
-   * Whether the pool is full or not.
-   * @returns The pool fullness boolean status.
-   */
-  protected get full (): boolean {
-    return (
-      this.workerNodes.length >=
-      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
-    )
-  }
-
-  /**
-   * Whether the pool is busy or not.
-   * @returns The pool busyness boolean status.
-   */
-  protected abstract get busy (): boolean
-
-  /**
-   * Whether worker nodes are executing concurrently their tasks quota or not.
-   * @returns Worker nodes busyness boolean status.
-   */
-  protected internalBusy (): boolean {
-    if (this.opts.enableTasksQueue === true) {
-      return (
-        this.workerNodes.findIndex(
-          workerNode =>
-            workerNode.info.ready &&
-            workerNode.usage.tasks.executing <
-              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              this.opts.tasksQueueOptions!.concurrency!
-        ) === -1
-      )
-    }
-    return (
-      this.workerNodes.findIndex(
-        workerNode =>
-          workerNode.info.ready && workerNode.usage.tasks.executing === 0
-      ) === -1
-    )
-  }
-
-  private isWorkerNodeIdle (workerNodeKey: number): boolean {
-    if (this.opts.enableTasksQueue === true) {
-      return (
-        this.workerNodes[workerNodeKey].usage.tasks.executing === 0 &&
-        this.tasksQueueSize(workerNodeKey) === 0
-      )
-    }
-    return this.workerNodes[workerNodeKey].usage.tasks.executing === 0
-  }
-
-  private isWorkerNodeBusy (workerNodeKey: number): boolean {
-    if (this.opts.enableTasksQueue === true) {
-      return (
-        this.workerNodes[workerNodeKey].usage.tasks.executing >=
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.opts.tasksQueueOptions!.concurrency!
-      )
-    }
-    return this.workerNodes[workerNodeKey].usage.tasks.executing > 0
-  }
-
-  private async sendTaskFunctionOperationToWorker (
-    workerNodeKey: number,
-    message: MessageValue<Data>
-  ): Promise<boolean> {
-    return await new Promise<boolean>((resolve, reject) => {
-      const taskFunctionOperationListener = (
-        message: MessageValue<Response>
-      ): void => {
-        this.checkMessageWorkerId(message)
-        const workerId = this.getWorkerInfo(workerNodeKey)?.id
-        if (
-          message.taskFunctionOperationStatus != null &&
-          message.workerId === workerId
-        ) {
-          if (message.taskFunctionOperationStatus) {
-            resolve(true)
-          } else {
-            reject(
-              new Error(
-                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                `Task function operation '${message.taskFunctionOperation?.toString()}' failed on worker ${message.workerId?.toString()} with error: '${
-                  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                  message.workerError?.message
-                }'`
-              )
-            )
-          }
-          this.deregisterWorkerMessageListener(
-            this.getWorkerNodeKeyByWorkerId(message.workerId),
-            taskFunctionOperationListener
-          )
-        }
-      }
-      this.registerWorkerMessageListener(
-        workerNodeKey,
-        taskFunctionOperationListener
-      )
-      this.sendToWorker(workerNodeKey, message)
-    })
-  }
-
-  private async sendTaskFunctionOperationToWorkers (
-    message: MessageValue<Data>
-  ): Promise<boolean> {
-    return await new Promise<boolean>((resolve, reject) => {
-      const responsesReceived = new Array<MessageValue<Response>>()
-      const taskFunctionOperationsListener = (
-        message: MessageValue<Response>
-      ): void => {
-        this.checkMessageWorkerId(message)
-        if (message.taskFunctionOperationStatus != null) {
-          responsesReceived.push(message)
-          if (responsesReceived.length === this.workerNodes.length) {
-            if (
-              responsesReceived.every(
-                message => message.taskFunctionOperationStatus === true
-              )
-            ) {
-              resolve(true)
-            } else if (
-              responsesReceived.some(
-                message => message.taskFunctionOperationStatus === false
-              )
-            ) {
-              const errorResponse = responsesReceived.find(
-                response => response.taskFunctionOperationStatus === false
-              )
-              reject(
-                new Error(
-                  `Task function operation '${
-                    message.taskFunctionOperation as string
-                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                  }' failed on worker ${errorResponse?.workerId?.toString()} with error: '${
-                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-                    errorResponse?.workerError?.message
-                  }'`
-                )
-              )
-            }
-            this.deregisterWorkerMessageListener(
-              this.getWorkerNodeKeyByWorkerId(message.workerId),
-              taskFunctionOperationsListener
-            )
-          }
-        }
-      }
-      for (const workerNodeKey of this.workerNodes.keys()) {
-        this.registerWorkerMessageListener(
-          workerNodeKey,
-          taskFunctionOperationsListener
-        )
-        this.sendToWorker(workerNodeKey, message)
-      }
-    })
-  }
-
-  /** @inheritDoc */
-  public hasTaskFunction (name: string): boolean {
-    return this.listTaskFunctionsProperties().some(
-      taskFunctionProperties => taskFunctionProperties.name === name
-    )
-  }
-
-  /** @inheritDoc */
-  public async addTaskFunction (
-    name: string,
-    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
-  ): Promise<boolean> {
-    if (typeof name !== 'string') {
-      throw new TypeError('name argument must be a string')
-    }
-    if (typeof name === 'string' && name.trim().length === 0) {
-      throw new TypeError('name argument must not be an empty string')
-    }
-    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')
-    }
-    checkValidPriority(fn.priority)
-    checkValidWorkerChoiceStrategy(fn.strategy)
-    const opResult = await this.sendTaskFunctionOperationToWorkers({
-      taskFunctionOperation: 'add',
-      taskFunctionProperties: buildTaskFunctionProperties(name, fn),
-      taskFunction: fn.taskFunction.toString(),
-    })
-    this.taskFunctions.set(name, fn)
-    this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
-      this.getWorkerChoiceStrategies()
-    )
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.sendStatisticsMessageToWorker(workerNodeKey)
-    }
-    return opResult
-  }
-
-  /** @inheritDoc */
-  public async removeTaskFunction (name: string): Promise<boolean> {
-    if (!this.taskFunctions.has(name)) {
-      throw new Error(
-        'Cannot remove a task function not handled on the pool side'
-      )
-    }
-    const opResult = await this.sendTaskFunctionOperationToWorkers({
-      taskFunctionOperation: 'remove',
-      taskFunctionProperties: buildTaskFunctionProperties(
-        name,
-        this.taskFunctions.get(name)
-      ),
-    })
-    for (const workerNode of this.workerNodes) {
-      workerNode.deleteTaskFunctionWorkerUsage(name)
-    }
-    this.taskFunctions.delete(name)
-    this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
-      this.getWorkerChoiceStrategies()
+      [this.opts.workerChoiceStrategy!],
+      this.opts.workerChoiceStrategyOptions
     )
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.sendStatisticsMessageToWorker(workerNodeKey)
-    }
-    return opResult
-  }
 
-  /** @inheritDoc */
-  public listTaskFunctionsProperties (): TaskFunctionProperties[] {
-    for (const workerNode of this.workerNodes) {
-      if (
-        Array.isArray(workerNode.info.taskFunctionsProperties) &&
-        workerNode.info.taskFunctionsProperties.length > 0
-      ) {
-        return workerNode.info.taskFunctionsProperties
-      }
-    }
-    return []
-  }
+    this.setupHook()
 
-  /**
-   * Gets task function worker choice 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 getTaskFunctionWorkerChoiceStrategy = (
-    name?: string
-  ): WorkerChoiceStrategy | undefined => {
-    name = name ?? DEFAULT_TASK_NAME
-    const taskFunctionsProperties = this.listTaskFunctionsProperties()
-    if (name === DEFAULT_TASK_NAME) {
-      name = taskFunctionsProperties[1]?.name
-    }
-    return taskFunctionsProperties.find(
-      (taskFunctionProperties: TaskFunctionProperties) =>
-        taskFunctionProperties.name === name
-    )?.strategy
-  }
+    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
 
-  /**
-   * Gets worker node task function worker choice strategy, if any.
-   * @param workerNodeKey - The worker node key.
-   * @param name - The task function name.
-   * @returns The worker node task function worker choice strategy if the worker node task function worker choice strategy is defined, `undefined` otherwise.
-   */
-  private readonly getWorkerNodeTaskFunctionWorkerChoiceStrategy = (
-    workerNodeKey: number,
-    name?: string
-  ): WorkerChoiceStrategy | undefined => {
-    const workerInfo = this.getWorkerInfo(workerNodeKey)
-    if (workerInfo == null) {
-      return
-    }
-    name = name ?? DEFAULT_TASK_NAME
-    if (name === DEFAULT_TASK_NAME) {
-      name = workerInfo.taskFunctionsProperties?.[1]?.name
+    this.started = false
+    this.starting = false
+    this.destroying = false
+    this.readyEventEmitted = false
+    this.startingMinimumNumberOfWorkers = false
+    if (this.opts.startWorkers === true) {
+      this.start()
     }
-    return workerInfo.taskFunctionsProperties?.find(
-      (taskFunctionProperties: TaskFunctionProperties) =>
-        taskFunctionProperties.name === name
-    )?.strategy
   }
 
   /**
-   * Gets worker node task function priority, if any.
+   * Hook executed after the worker task execution.
+   * Can be overridden.
    * @param workerNodeKey - The worker node key.
-   * @param name - The task function name.
-   * @returns The worker node task function priority if the worker node task function priority is defined, `undefined` otherwise.
+   * @param message - The received message.
    */
-  private readonly getWorkerNodeTaskFunctionPriority = (
+  protected afterTaskExecutionHook (
     workerNodeKey: number,
-    name?: string
-  ): number | undefined => {
-    const workerInfo = this.getWorkerInfo(workerNodeKey)
-    if (workerInfo == null) {
-      return
-    }
-    name = name ?? DEFAULT_TASK_NAME
-    if (name === DEFAULT_TASK_NAME) {
-      name = workerInfo.taskFunctionsProperties?.[1]?.name
-    }
-    return workerInfo.taskFunctionsProperties?.find(
-      (taskFunctionProperties: TaskFunctionProperties) =>
-        taskFunctionProperties.name === name
-    )?.priority
-  }
-
-  /**
-   * Gets the worker choice strategies registered in this pool.
-   * @returns The worker choice strategies.
-   */
-  private readonly getWorkerChoiceStrategies =
-    (): 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
-          ),
-      ])
-    }
-
-  /** @inheritDoc */
-  public async setDefaultTaskFunction (name: string): Promise<boolean> {
-    return await this.sendTaskFunctionOperationToWorkers({
-      taskFunctionOperation: 'default',
-      taskFunctionProperties: buildTaskFunctionProperties(
-        name,
-        this.taskFunctions.get(name)
-      ),
-    })
-  }
-
-  private shallExecuteTask (workerNodeKey: number): boolean {
-    return (
-      this.tasksQueueSize(workerNodeKey) === 0 &&
-      this.workerNodes[workerNodeKey].usage.tasks.executing <
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.opts.tasksQueueOptions!.concurrency!
-    )
-  }
-
-  private async internalExecute (
-    data?: Data,
-    name?: string,
-    transferList?: readonly TransferListItem[]
-  ): Promise<Response> {
-    return await new Promise<Response>((resolve, reject) => {
-      const timestamp = performance.now()
-      const workerNodeKey = this.chooseWorkerNode(name)
-      const task: Task<Data> = {
-        name: name ?? DEFAULT_TASK_NAME,
-        data: data ?? ({} as Data),
-        priority: this.getWorkerNodeTaskFunctionPriority(workerNodeKey, name),
-        strategy: this.getWorkerNodeTaskFunctionWorkerChoiceStrategy(
-          workerNodeKey,
-          name
-        ),
-        transferList,
-        timestamp,
-        taskId: randomUUID(),
-      }
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.promiseResponseMap.set(task.taskId!, {
-        resolve,
-        reject,
-        workerNodeKey,
-        ...(this.emitter != null && {
-          asyncResource: new AsyncResource('poolifier:task', {
-            triggerAsyncId: this.emitter.asyncId,
-            requireManualDestroy: true,
-          }),
-        }),
-      })
-      if (
-        this.opts.enableTasksQueue === false ||
-        (this.opts.enableTasksQueue === true &&
-          this.shallExecuteTask(workerNodeKey))
-      ) {
-        this.executeTask(workerNodeKey, task)
-      } else {
-        this.enqueueTask(workerNodeKey, task)
-      }
-    })
-  }
-
-  /** @inheritDoc */
-  public async execute (
-    data?: Data,
-    name?: string,
-    transferList?: readonly TransferListItem[]
-  ): Promise<Response> {
-    if (!this.started) {
-      throw new Error('Cannot execute a task on not started pool')
-    }
-    if (this.destroying) {
-      throw new Error('Cannot execute a task on destroying pool')
-    }
-    if (name != null && typeof name !== 'string') {
-      throw new TypeError('name argument must be a string')
-    }
-    if (name != null && typeof name === 'string' && name.trim().length === 0) {
-      throw new TypeError('name argument must not be an empty string')
-    }
-    if (transferList != null && !Array.isArray(transferList)) {
-      throw new TypeError('transferList argument must be an array')
-    }
-    return await this.internalExecute(data, name, transferList)
-  }
-
-  /** @inheritDoc */
-  public async mapExecute (
-    data: Iterable<Data>,
-    name?: string,
-    transferList?: readonly TransferListItem[]
-  ): Promise<Response[]> {
-    if (!this.started) {
-      throw new Error('Cannot execute task(s) on not started pool')
-    }
-    if (this.destroying) {
-      throw new Error('Cannot execute task(s) on destroying pool')
-    }
+    message: MessageValue<Response>
+  ): void {
+    let needWorkerChoiceStrategiesUpdate = false
     // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (data == null) {
-      throw new TypeError('data argument must be a defined iterable')
-    }
-    if (typeof data[Symbol.iterator] !== 'function') {
-      throw new TypeError('data argument must be an iterable')
-    }
-    if (name != null && typeof name !== 'string') {
-      throw new TypeError('name argument must be a string')
-    }
-    if (name != null && typeof name === 'string' && name.trim().length === 0) {
-      throw new TypeError('name argument must not be an empty string')
+    if (this.workerNodes[workerNodeKey]?.usage != null) {
+      const workerUsage = this.workerNodes[workerNodeKey].usage
+      updateTaskStatisticsWorkerUsage(workerUsage, message)
+      updateRunTimeWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        workerUsage,
+        message
+      )
+      updateEluWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        workerUsage,
+        message
+      )
+      needWorkerChoiceStrategiesUpdate = true
     }
-    if (transferList != null && !Array.isArray(transferList)) {
-      throw new TypeError('transferList argument must be an array')
+    if (
+      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
+      message.taskPerformance?.name != null &&
+      this.workerNodes[workerNodeKey].getTaskFunctionWorkerUsage(
+        message.taskPerformance.name
+      ) != null
+    ) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const taskFunctionWorkerUsage = this.workerNodes[
+        workerNodeKey
+      ].getTaskFunctionWorkerUsage(message.taskPerformance.name)!
+      updateTaskStatisticsWorkerUsage(taskFunctionWorkerUsage, message)
+      updateRunTimeWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        taskFunctionWorkerUsage,
+        message
+      )
+      updateEluWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        taskFunctionWorkerUsage,
+        message
+      )
+      needWorkerChoiceStrategiesUpdate = true
     }
-    if (!Array.isArray(data)) {
-      data = [...data]
+    if (needWorkerChoiceStrategiesUpdate) {
+      this.workerChoiceStrategiesContext?.update(workerNodeKey)
     }
-    return await Promise.all(
-      (data as Data[]).map(data =>
-        this.internalExecute(data, name, transferList)
-      )
-    )
   }
 
   /**
-   * Starts the minimum number of workers.
-   * @param initWorkerNodeUsage - Whether to initialize the worker node usage or not. @defaultValue false
+   * Method hooked up after a worker node has been newly created.
+   * Can be overridden.
+   * @param workerNodeKey - The newly created worker node key.
    */
-  private startMinimumNumberOfWorkers (initWorkerNodeUsage = false): void {
-    if (this.minimumNumberOfWorkers === 0) {
-      return
-    }
-    this.startingMinimumNumberOfWorkers = true
-    while (
-      this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          !workerNode.info.dynamic ? accumulator + 1 : accumulator,
-        0
-      ) < this.minimumNumberOfWorkers
-    ) {
-      const workerNodeKey = this.createAndSetupWorkerNode()
-      initWorkerNodeUsage &&
-        this.initWorkerNodeUsage(this.workerNodes[workerNodeKey])
+  protected afterWorkerNodeSetup (workerNodeKey: number): void {
+    // Listen to worker messages.
+    this.registerWorkerMessageListener(
+      workerNodeKey,
+      this.workerMessageListener
+    )
+    // Send the startup message to worker.
+    this.sendStartupMessageToWorker(workerNodeKey)
+    // Send the statistics message to worker.
+    this.sendStatisticsMessageToWorker(workerNodeKey)
+    if (this.opts.enableTasksQueue === true) {
+      if (this.opts.tasksQueueOptions?.taskStealing === true) {
+        this.workerNodes[workerNodeKey].on(
+          'idle',
+          this.handleWorkerNodeIdleEvent
+        )
+      }
+      if (this.opts.tasksQueueOptions?.tasksStealingOnBackPressure === true) {
+        this.workerNodes[workerNodeKey].on(
+          'backPressure',
+          this.handleWorkerNodeBackPressureEvent
+        )
+      }
     }
-    this.startingMinimumNumberOfWorkers = false
   }
 
-  /** @inheritdoc */
-  public start (): void {
-    if (this.started) {
-      throw new Error('Cannot start an already started pool')
-    }
-    if (this.starting) {
-      throw new Error('Cannot start an already starting pool')
+  /**
+   * Hook executed before the worker task execution.
+   * Can be overridden.
+   * @param workerNodeKey - The worker node key.
+   * @param task - The task to execute.
+   */
+  protected beforeTaskExecutionHook (
+    workerNodeKey: number,
+    task: Task<Data>
+  ): void {
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (this.workerNodes[workerNodeKey]?.usage != null) {
+      const workerUsage = this.workerNodes[workerNodeKey].usage
+      ++workerUsage.tasks.executing
+      updateWaitTimeWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        workerUsage,
+        task
+      )
     }
-    if (this.destroying) {
-      throw new Error('Cannot start a destroying pool')
+    if (
+      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.workerNodes[workerNodeKey].getTaskFunctionWorkerUsage(task.name!) !=
+        null
+    ) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      const taskFunctionWorkerUsage = this.workerNodes[
+        workerNodeKey
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      ].getTaskFunctionWorkerUsage(task.name!)!
+      ++taskFunctionWorkerUsage.tasks.executing
+      updateWaitTimeWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        taskFunctionWorkerUsage,
+        task
+      )
     }
-    this.starting = true
-    this.startMinimumNumberOfWorkers()
-    this.startTimestamp = performance.now()
-    this.starting = false
-    this.started = true
   }
 
-  /** @inheritDoc */
-  public async destroy (): Promise<void> {
-    if (!this.started) {
-      throw new Error('Cannot destroy an already destroyed pool')
-    }
-    if (this.starting) {
-      throw new Error('Cannot destroy an starting pool')
+  /**
+   * Emits dynamic worker creation events.
+   */
+  protected abstract checkAndEmitDynamicWorkerCreationEvents (): void
+
+  /**
+   * Creates a new, completely set up dynamic worker node.
+   * @returns New, completely set up dynamic worker node key.
+   */
+  protected createAndSetupDynamicWorkerNode (): number {
+    const workerNodeKey = this.createAndSetupWorkerNode()
+    this.registerWorkerMessageListener(workerNodeKey, message => {
+      this.checkMessageWorkerId(message)
+      const localWorkerNodeKey = this.getWorkerNodeKeyByWorkerId(
+        message.workerId
+      )
+      const workerInfo = this.getWorkerInfo(localWorkerNodeKey)
+      // Kill message received from worker
+      if (
+        isKillBehavior(KillBehaviors.HARD, message.kill) ||
+        (isKillBehavior(KillBehaviors.SOFT, message.kill) &&
+          this.isWorkerNodeIdle(localWorkerNodeKey) &&
+          workerInfo != null &&
+          !workerInfo.continuousStealing &&
+          !workerInfo.backPressureStealing)
+      ) {
+        // Flag the worker node as not ready immediately
+        this.flagWorkerNodeAsNotReady(localWorkerNodeKey)
+        this.destroyWorkerNode(localWorkerNodeKey).catch((error: unknown) => {
+          this.emitter?.emit(PoolEvents.error, error)
+        })
+      }
+    })
+    this.sendToWorker(workerNodeKey, {
+      checkActive: true,
+    })
+    if (this.taskFunctions.size > 0) {
+      for (const [taskFunctionName, taskFunctionObject] of this.taskFunctions) {
+        this.sendTaskFunctionOperationToWorker(workerNodeKey, {
+          taskFunction: taskFunctionObject.taskFunction.toString(),
+          taskFunctionOperation: 'add',
+          taskFunctionProperties: buildTaskFunctionProperties(
+            taskFunctionName,
+            taskFunctionObject
+          ),
+        }).catch((error: unknown) => {
+          this.emitter?.emit(PoolEvents.error, error)
+        })
+      }
     }
-    if (this.destroying) {
-      throw new Error('Cannot destroy an already destroying pool')
+    const workerNode = this.workerNodes[workerNodeKey]
+    workerNode.info.dynamic = true
+    if (
+      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerReady ===
+        true ||
+      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerUsage ===
+        true
+    ) {
+      workerNode.info.ready = true
     }
-    this.destroying = true
-    await Promise.all(
-      this.workerNodes.map(async (_, workerNodeKey) => {
-        await this.destroyWorkerNode(workerNodeKey)
-      })
-    )
-    this.emitter?.emit(PoolEvents.destroy, this.info)
-    this.emitter?.emitDestroy()
-    this.readyEventEmitted = false
-    delete this.startTimestamp
-    this.destroying = false
-    this.started = false
+    this.initWorkerNodeUsage(workerNode)
+    this.checkAndEmitDynamicWorkerCreationEvents()
+    return workerNodeKey
   }
 
-  private async sendKillMessageToWorker (workerNodeKey: number): Promise<void> {
-    await new Promise<void>((resolve, reject) => {
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-      if (this.workerNodes[workerNodeKey] == null) {
-        resolve()
-        return
-      }
-      const killMessageListener = (message: MessageValue<Response>): void => {
-        this.checkMessageWorkerId(message)
-        if (message.kill === 'success') {
-          resolve()
-        } else if (message.kill === 'failure') {
-          reject(
-            new Error(
-              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-              `Kill message handling failed on worker ${message.workerId?.toString()}`
-            )
-          )
+  /**
+   * Creates a new, completely set up worker node.
+   * @returns New, completely set up worker node key.
+   */
+  protected createAndSetupWorkerNode (): number {
+    const workerNode = this.createWorkerNode()
+    workerNode.registerWorkerEventHandler(
+      'online',
+      this.opts.onlineHandler ?? EMPTY_FUNCTION
+    )
+    workerNode.registerWorkerEventHandler(
+      'message',
+      this.opts.messageHandler ?? EMPTY_FUNCTION
+    )
+    workerNode.registerWorkerEventHandler(
+      'error',
+      this.opts.errorHandler ?? EMPTY_FUNCTION
+    )
+    workerNode.registerOnceWorkerEventHandler('error', (error: Error) => {
+      workerNode.info.ready = false
+      this.emitter?.emit(PoolEvents.error, error)
+      if (
+        this.started &&
+        !this.destroying &&
+        this.opts.restartWorkerOnError === true
+      ) {
+        if (workerNode.info.dynamic) {
+          this.createAndSetupDynamicWorkerNode()
+        } else if (!this.startingMinimumNumberOfWorkers) {
+          this.startMinimumNumberOfWorkers(true)
         }
       }
-      // FIXME: should be registered only once
-      this.registerWorkerMessageListener(workerNodeKey, killMessageListener)
-      this.sendToWorker(workerNodeKey, { kill: true })
+      if (
+        this.started &&
+        !this.destroying &&
+        this.opts.enableTasksQueue === true
+      ) {
+        this.redistributeQueuedTasks(this.workerNodes.indexOf(workerNode))
+      }
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, promise/no-promise-in-callback
+      workerNode?.terminate().catch((error: unknown) => {
+        this.emitter?.emit(PoolEvents.error, error)
+      })
+    })
+    workerNode.registerWorkerEventHandler(
+      'exit',
+      this.opts.exitHandler ?? EMPTY_FUNCTION
+    )
+    workerNode.registerOnceWorkerEventHandler('exit', () => {
+      this.removeWorkerNode(workerNode)
+      if (
+        this.started &&
+        !this.startingMinimumNumberOfWorkers &&
+        !this.destroying
+      ) {
+        this.startMinimumNumberOfWorkers(true)
+      }
     })
+    const workerNodeKey = this.addWorkerNode(workerNode)
+    this.afterWorkerNodeSetup(workerNodeKey)
+    return workerNodeKey
   }
 
+  /**
+   * Deregisters a listener callback on the worker given its worker node key.
+   * @param workerNodeKey - The worker node key.
+   * @param listener - The message listener callback.
+   */
+  protected abstract deregisterWorkerMessageListener<
+    Message extends Data | Response
+  >(
+    workerNodeKey: number,
+    listener: (message: MessageValue<Message>) => void
+  ): void
+
   /**
    * Terminates the worker node given its worker node key.
    * @param workerNodeKey - The worker node key.
@@ -1397,12 +770,55 @@ export abstract class AbstractPool<
     await workerNode.terminate()
   }
 
+  protected flagWorkerNodeAsNotReady (workerNodeKey: number): void {
+    const workerInfo = this.getWorkerInfo(workerNodeKey)
+    if (workerInfo != null) {
+      workerInfo.ready = false
+    }
+  }
+
+  protected flushTasksQueue (workerNodeKey: number): number {
+    let flushedTasks = 0
+    while (this.tasksQueueSize(workerNodeKey) > 0) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.executeTask(workerNodeKey, this.dequeueTask(workerNodeKey)!)
+      ++flushedTasks
+    }
+    this.workerNodes[workerNodeKey].clearTasksQueue()
+    return flushedTasks
+  }
+
   /**
-   * Setup hook to execute code before worker nodes are created in the abstract constructor.
-   * Can be overridden.
+   * Gets the worker information given its worker node key.
+   * @param workerNodeKey - The worker node key.
+   * @returns The worker information.
    */
-  protected setupHook (): void {
-    /* Intentionally empty */
+  protected getWorkerInfo (workerNodeKey: number): undefined | WorkerInfo {
+    return this.workerNodes[workerNodeKey]?.info
+  }
+
+  /**
+   * Whether worker nodes are executing concurrently their tasks quota or not.
+   * @returns Worker nodes busyness boolean status.
+   */
+  protected internalBusy (): boolean {
+    if (this.opts.enableTasksQueue === true) {
+      return (
+        this.workerNodes.findIndex(
+          workerNode =>
+            workerNode.info.ready &&
+            workerNode.usage.tasks.executing <
+              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+              this.opts.tasksQueueOptions!.concurrency!
+        ) === -1
+      )
+    }
+    return (
+      this.workerNodes.findIndex(
+        workerNode =>
+          workerNode.info.ready && workerNode.usage.tasks.executing === 0
+      ) === -1
+    )
   }
 
   /**
@@ -1412,115 +828,219 @@ export abstract class AbstractPool<
   protected abstract isMain (): boolean
 
   /**
-   * Hook executed before the worker task execution.
-   * Can be overridden.
+   * Registers once a listener callback on the worker given its worker node key.
    * @param workerNodeKey - The worker node key.
-   * @param task - The task to execute.
+   * @param listener - The message listener callback.
    */
-  protected beforeTaskExecutionHook (
+  protected abstract registerOnceWorkerMessageListener<
+    Message extends Data | Response
+  >(
     workerNodeKey: number,
-    task: Task<Data>
+    listener: (message: MessageValue<Message>) => void
+  ): void
+
+  /**
+   * Registers a listener callback on the worker given its worker node key.
+   * @param workerNodeKey - The worker node key.
+   * @param listener - The message listener callback.
+   */
+  protected abstract registerWorkerMessageListener<
+    Message extends Data | Response
+  >(
+    workerNodeKey: number,
+    listener: (message: MessageValue<Message>) => void
+  ): void
+
+  /**
+   * Sends the startup message to worker given its worker node key.
+   * @param workerNodeKey - The worker node key.
+   */
+  protected abstract sendStartupMessageToWorker (workerNodeKey: number): void
+
+  /**
+   * Sends a message to worker given its worker node key.
+   * @param workerNodeKey - The worker node key.
+   * @param message - The message.
+   * @param transferList - The optional array of transferable objects.
+   */
+  protected abstract sendToWorker (
+    workerNodeKey: number,
+    message: MessageValue<Data>,
+    transferList?: readonly TransferListItem[]
+  ): void
+
+  /**
+   * Setup hook to execute code before worker nodes are created in the abstract constructor.
+   * Can be overridden.
+   */
+  protected setupHook (): void {
+    /* Intentionally empty */
+  }
+
+  /**
+   * Conditions for dynamic worker creation.
+   * @returns Whether to create a dynamic worker or not.
+   */
+  protected abstract shallCreateDynamicWorker (): boolean
+
+  /**
+   * Adds the given worker node in the pool worker nodes.
+   * @param workerNode - The worker node.
+   * @returns The added worker node key.
+   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the added worker node is not found.
+   */
+  private addWorkerNode (workerNode: IWorkerNode<Worker, Data>): number {
+    this.workerNodes.push(workerNode)
+    const workerNodeKey = this.workerNodes.indexOf(workerNode)
+    if (workerNodeKey === -1) {
+      throw new Error('Worker added not found in worker nodes')
+    }
+    return workerNodeKey
+  }
+
+  private buildTasksQueueOptions (
+    tasksQueueOptions: TasksQueueOptions | undefined
+  ): TasksQueueOptions {
+    return {
+      ...getDefaultTasksQueueOptions(
+        this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
+      ),
+      ...this.opts.tasksQueueOptions,
+      ...tasksQueueOptions,
+    }
+  }
+
+  private cannotStealTask (): boolean {
+    return this.workerNodes.length <= 1 || this.info.queuedTasks === 0
+  }
+
+  private checkAndEmitEmptyEvent (): void {
+    if (this.empty) {
+      this.emitter?.emit(PoolEvents.empty, this.info)
+      this.readyEventEmitted = false
+    }
+  }
+
+  private checkAndEmitReadyEvent (): void {
+    if (!this.readyEventEmitted && this.ready) {
+      this.emitter?.emit(PoolEvents.ready, this.info)
+      this.readyEventEmitted = true
+    }
+  }
+
+  private checkAndEmitTaskExecutionEvents (): void {
+    if (this.busy) {
+      this.emitter?.emit(PoolEvents.busy, this.info)
+    }
+  }
+
+  private checkAndEmitTaskQueuingEvents (): void {
+    if (this.hasBackPressure()) {
+      this.emitter?.emit(PoolEvents.backPressure, this.info)
+    }
+  }
+
+  /**
+   * Checks if the worker id sent in the received message from a worker is valid.
+   * @param message - The received message.
+   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the worker id is invalid.
+   */
+  private checkMessageWorkerId (message: MessageValue<Data | Response>): void {
+    if (message.workerId == null) {
+      throw new Error('Worker message received without worker id')
+    } else if (this.getWorkerNodeKeyByWorkerId(message.workerId) === -1) {
+      throw new Error(
+        `Worker message received from unknown worker '${message.workerId.toString()}'`
+      )
+    }
+  }
+
+  private checkMinimumNumberOfWorkers (
+    minimumNumberOfWorkers: number | undefined
   ): void {
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (this.workerNodes[workerNodeKey]?.usage != null) {
-      const workerUsage = this.workerNodes[workerNodeKey].usage
-      ++workerUsage.tasks.executing
-      updateWaitTimeWorkerUsage(
-        this.workerChoiceStrategiesContext,
-        workerUsage,
-        task
+    if (minimumNumberOfWorkers == null) {
+      throw new Error(
+        'Cannot instantiate a pool without specifying the number of workers'
+      )
+    } else if (!Number.isSafeInteger(minimumNumberOfWorkers)) {
+      throw new TypeError(
+        'Cannot instantiate a pool with a non safe integer number of workers'
+      )
+    } else if (minimumNumberOfWorkers < 0) {
+      throw new RangeError(
+        'Cannot instantiate a pool with a negative number of workers'
+      )
+    } else if (this.type === PoolTypes.fixed && minimumNumberOfWorkers === 0) {
+      throw new RangeError('Cannot instantiate a fixed pool with zero worker')
+    }
+  }
+
+  private checkPoolOptions (opts: PoolOptions<Worker>): void {
+    if (isPlainObject(opts)) {
+      this.opts.startWorkers = opts.startWorkers ?? true
+      checkValidWorkerChoiceStrategy(opts.workerChoiceStrategy)
+      this.opts.workerChoiceStrategy =
+        opts.workerChoiceStrategy ?? WorkerChoiceStrategies.ROUND_ROBIN
+      this.checkValidWorkerChoiceStrategyOptions(
+        opts.workerChoiceStrategyOptions
       )
+      if (opts.workerChoiceStrategyOptions != null) {
+        this.opts.workerChoiceStrategyOptions = opts.workerChoiceStrategyOptions
+      }
+      this.opts.restartWorkerOnError = opts.restartWorkerOnError ?? true
+      this.opts.enableEvents = opts.enableEvents ?? true
+      this.opts.enableTasksQueue = opts.enableTasksQueue ?? false
+      if (this.opts.enableTasksQueue) {
+        checkValidTasksQueueOptions(opts.tasksQueueOptions)
+        this.opts.tasksQueueOptions = this.buildTasksQueueOptions(
+          opts.tasksQueueOptions
+        )
+      }
+    } else {
+      throw new TypeError('Invalid pool options: must be a plain object')
     }
-    if (
-      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.workerNodes[workerNodeKey].getTaskFunctionWorkerUsage(task.name!) !=
-        null
-    ) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      const taskFunctionWorkerUsage = this.workerNodes[
-        workerNodeKey
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ].getTaskFunctionWorkerUsage(task.name!)!
-      ++taskFunctionWorkerUsage.tasks.executing
-      updateWaitTimeWorkerUsage(
-        this.workerChoiceStrategiesContext,
-        taskFunctionWorkerUsage,
-        task
+  }
+
+  private checkPoolType (): void {
+    if (this.type === PoolTypes.fixed && this.maximumNumberOfWorkers != null) {
+      throw new Error(
+        'Cannot instantiate a fixed pool with a maximum number of workers specified at initialization'
       )
     }
   }
 
-  /**
-   * Hook executed after the worker task execution.
-   * Can be overridden.
-   * @param workerNodeKey - The worker node key.
-   * @param message - The received message.
-   */
-  protected afterTaskExecutionHook (
-    workerNodeKey: number,
-    message: MessageValue<Response>
+  private checkValidWorkerChoiceStrategyOptions (
+    workerChoiceStrategyOptions: undefined | WorkerChoiceStrategyOptions
   ): void {
-    let needWorkerChoiceStrategiesUpdate = false
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (this.workerNodes[workerNodeKey]?.usage != null) {
-      const workerUsage = this.workerNodes[workerNodeKey].usage
-      updateTaskStatisticsWorkerUsage(workerUsage, message)
-      updateRunTimeWorkerUsage(
-        this.workerChoiceStrategiesContext,
-        workerUsage,
-        message
-      )
-      updateEluWorkerUsage(
-        this.workerChoiceStrategiesContext,
-        workerUsage,
-        message
+    if (
+      workerChoiceStrategyOptions != null &&
+      !isPlainObject(workerChoiceStrategyOptions)
+    ) {
+      throw new TypeError(
+        'Invalid worker choice strategy options: must be a plain object'
       )
-      needWorkerChoiceStrategiesUpdate = true
     }
     if (
-      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
-      message.taskPerformance?.name != null &&
-      this.workerNodes[workerNodeKey].getTaskFunctionWorkerUsage(
-        message.taskPerformance.name
-      ) != null
+      workerChoiceStrategyOptions?.weights != null &&
+      Object.keys(workerChoiceStrategyOptions.weights).length !==
+        (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
     ) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      const taskFunctionWorkerUsage = this.workerNodes[
-        workerNodeKey
-      ].getTaskFunctionWorkerUsage(message.taskPerformance.name)!
-      updateTaskStatisticsWorkerUsage(taskFunctionWorkerUsage, message)
-      updateRunTimeWorkerUsage(
-        this.workerChoiceStrategiesContext,
-        taskFunctionWorkerUsage,
-        message
-      )
-      updateEluWorkerUsage(
-        this.workerChoiceStrategiesContext,
-        taskFunctionWorkerUsage,
-        message
+      throw new Error(
+        'Invalid worker choice strategy options: must have a weight for each worker node'
       )
-      needWorkerChoiceStrategiesUpdate = true
     }
-    if (needWorkerChoiceStrategiesUpdate) {
-      this.workerChoiceStrategiesContext?.update(workerNodeKey)
+    if (
+      workerChoiceStrategyOptions?.measurement != null &&
+      !Object.values(Measurements).includes(
+        workerChoiceStrategyOptions.measurement
+      )
+    ) {
+      throw new Error(
+        `Invalid worker choice strategy options: invalid measurement '${workerChoiceStrategyOptions.measurement}'`
+      )
     }
   }
 
-  /**
-   * Whether the worker node shall update its task function worker usage or not.
-   * @param workerNodeKey - The worker node key.
-   * @returns `true` if the worker node shall update its task function worker usage, `false` otherwise.
-   */
-  private shallUpdateTaskFunctionWorkerUsage (workerNodeKey: number): boolean {
-    const workerInfo = this.getWorkerInfo(workerNodeKey)
-    return (
-      workerInfo != null &&
-      Array.isArray(workerInfo.taskFunctionsProperties) &&
-      workerInfo.taskFunctionsProperties.length > 2
-    )
-  }
-
   /**
    * Chooses a worker node for the next task.
    * @param name - The task function name.
@@ -1542,23 +1062,162 @@ export abstract class AbstractPool<
     )
   }
 
-  /**
-   * Conditions for dynamic worker creation.
-   * @returns Whether to create a dynamic worker or not.
-   */
-  protected abstract shallCreateDynamicWorker (): boolean
-
-  /**
-   * Sends a message to worker given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @param message - The message.
-   * @param transferList - The optional array of transferable objects.
-   */
-  protected abstract sendToWorker (
-    workerNodeKey: number,
-    message: MessageValue<Data>,
-    transferList?: readonly TransferListItem[]
-  ): void
+  /**
+   * Creates a worker node.
+   * @returns The created worker node.
+   */
+  private createWorkerNode (): IWorkerNode<Worker, Data> {
+    const workerNode = new WorkerNode<Worker, Data>(
+      this.worker,
+      this.filePath,
+      {
+        env: this.opts.env,
+        tasksQueueBackPressureSize:
+          this.opts.tasksQueueOptions?.size ??
+          getDefaultTasksQueueOptions(
+            this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
+          ).size,
+        tasksQueueBucketSize: defaultBucketSize,
+        tasksQueuePriority: this.getTasksQueuePriority(),
+        workerOptions: this.opts.workerOptions,
+      }
+    )
+    // Flag the worker node as ready at pool startup.
+    if (this.starting) {
+      workerNode.info.ready = true
+    }
+    return workerNode
+  }
+
+  private dequeueTask (workerNodeKey: number): Task<Data> | undefined {
+    return this.workerNodes[workerNodeKey].dequeueTask()
+  }
+
+  private enqueueTask (workerNodeKey: number, task: Task<Data>): number {
+    const tasksQueueSize = this.workerNodes[workerNodeKey].enqueueTask(task)
+    this.checkAndEmitTaskQueuingEvents()
+    return tasksQueueSize
+  }
+
+  /**
+   * Executes the given task on the worker given its worker node key.
+   * @param workerNodeKey - The worker node key.
+   * @param task - The task to execute.
+   */
+  private executeTask (workerNodeKey: number, task: Task<Data>): void {
+    this.beforeTaskExecutionHook(workerNodeKey, task)
+    this.sendToWorker(workerNodeKey, task, task.transferList)
+    this.checkAndEmitTaskExecutionEvents()
+  }
+
+  private flushTasksQueues (): void {
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.flushTasksQueue(workerNodeKey)
+    }
+  }
+
+  private getTasksQueuePriority (): boolean {
+    return this.listTaskFunctionsProperties().some(
+      taskFunctionProperties => taskFunctionProperties.priority != null
+    )
+  }
+
+  /**
+   * Gets the worker node key given its worker id.
+   * @param workerId - The worker id.
+   * @returns The worker node key if the worker id is found in the pool worker nodes, `-1` otherwise.
+   */
+  private getWorkerNodeKeyByWorkerId (workerId: number | undefined): number {
+    return this.workerNodes.findIndex(
+      workerNode => workerNode.info.id === workerId
+    )
+  }
+
+  private handleTask (workerNodeKey: number, task: Task<Data>): void {
+    if (this.shallExecuteTask(workerNodeKey)) {
+      this.executeTask(workerNodeKey, task)
+    } else {
+      this.enqueueTask(workerNodeKey, task)
+    }
+  }
+
+  private handleTaskExecutionResponse (message: MessageValue<Response>): void {
+    const { data, taskId, workerError } = message
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const promiseResponse = this.promiseResponseMap.get(taskId!)
+    if (promiseResponse != null) {
+      const { asyncResource, reject, resolve, workerNodeKey } = promiseResponse
+      const workerNode = this.workerNodes[workerNodeKey]
+      if (workerError != null) {
+        this.emitter?.emit(PoolEvents.taskError, workerError)
+        asyncResource != null
+          ? asyncResource.runInAsyncScope(
+            reject,
+            this.emitter,
+            workerError.message
+          )
+          : reject(workerError.message)
+      } else {
+        asyncResource != null
+          ? asyncResource.runInAsyncScope(resolve, this.emitter, data)
+          : resolve(data as Response)
+      }
+      asyncResource?.emitDestroy()
+      this.afterTaskExecutionHook(workerNodeKey, message)
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.promiseResponseMap.delete(taskId!)
+      if (this.opts.enableTasksQueue === true && !this.destroying) {
+        const workerNodeTasksUsage = workerNode.usage.tasks
+        if (
+          this.tasksQueueSize(workerNodeKey) > 0 &&
+          workerNodeTasksUsage.executing <
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            this.opts.tasksQueueOptions!.concurrency!
+        ) {
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.executeTask(workerNodeKey, this.dequeueTask(workerNodeKey)!)
+        }
+        if (this.isWorkerNodeIdle(workerNodeKey)) {
+          workerNode.emit('idle', {
+            workerNodeKey,
+          })
+        }
+      }
+      // FIXME: cannot be theoretically undefined. Schedule in the next tick to avoid race conditions?
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+      workerNode?.emit('taskFinished', taskId)
+    }
+  }
+
+  private handleWorkerReadyResponse (message: MessageValue<Response>): void {
+    const { ready, taskFunctionsProperties, workerId } = message
+    if (ready == null || !ready) {
+      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+      throw new Error(`Worker ${workerId?.toString()} failed to initialize`)
+    }
+    const workerNodeKey = this.getWorkerNodeKeyByWorkerId(workerId)
+    const workerNode = this.workerNodes[workerNodeKey]
+    workerNode.info.ready = ready
+    workerNode.info.taskFunctionsProperties = taskFunctionsProperties
+    this.sendStatisticsMessageToWorker(workerNodeKey)
+    this.setTasksQueuePriority(workerNodeKey)
+    this.checkAndEmitReadyEvent()
+  }
+
+  private hasBackPressure (): boolean {
+    return (
+      this.opts.enableTasksQueue === true &&
+      this.workerNodes.findIndex(
+        workerNode => !workerNode.hasBackPressure()
+      ) === -1
+    )
+  }
+
+  private initEventEmitter (): void {
+    this.emitter = new EventEmitterAsyncResource({
+      name: `poolifier:${this.type}-${this.worker}-pool`,
+    })
+  }
 
   /**
    * Initializes the worker node usage with sensible default values gathered during runtime.
@@ -1600,201 +1259,154 @@ export abstract class AbstractPool<
     }
   }
 
-  /**
-   * Creates a new, completely set up worker node.
-   * @returns New, completely set up worker node key.
-   */
-  protected createAndSetupWorkerNode (): number {
-    const workerNode = this.createWorkerNode()
-    workerNode.registerWorkerEventHandler(
-      'online',
-      this.opts.onlineHandler ?? EMPTY_FUNCTION
-    )
-    workerNode.registerWorkerEventHandler(
-      'message',
-      this.opts.messageHandler ?? EMPTY_FUNCTION
-    )
-    workerNode.registerWorkerEventHandler(
-      'error',
-      this.opts.errorHandler ?? EMPTY_FUNCTION
-    )
-    workerNode.registerOnceWorkerEventHandler('error', (error: Error) => {
-      workerNode.info.ready = false
-      this.emitter?.emit(PoolEvents.error, error)
-      if (
-        this.started &&
-        !this.destroying &&
-        this.opts.restartWorkerOnError === true
-      ) {
-        if (workerNode.info.dynamic) {
-          this.createAndSetupDynamicWorkerNode()
-        } else if (!this.startingMinimumNumberOfWorkers) {
-          this.startMinimumNumberOfWorkers(true)
-        }
-      }
-      if (
-        this.started &&
-        !this.destroying &&
-        this.opts.enableTasksQueue === true
-      ) {
-        this.redistributeQueuedTasks(this.workerNodes.indexOf(workerNode))
+  private async internalExecute (
+    data?: Data,
+    name?: string,
+    transferList?: readonly TransferListItem[]
+  ): Promise<Response> {
+    return await new Promise<Response>((resolve, reject) => {
+      const timestamp = performance.now()
+      const workerNodeKey = this.chooseWorkerNode(name)
+      const task: Task<Data> = {
+        data: data ?? ({} as Data),
+        name: name ?? DEFAULT_TASK_NAME,
+        priority: this.getWorkerNodeTaskFunctionPriority(workerNodeKey, name),
+        strategy: this.getWorkerNodeTaskFunctionWorkerChoiceStrategy(
+          workerNodeKey,
+          name
+        ),
+        taskId: randomUUID(),
+        timestamp,
+        transferList,
       }
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, promise/no-promise-in-callback
-      workerNode?.terminate().catch((error: unknown) => {
-        this.emitter?.emit(PoolEvents.error, error)
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.promiseResponseMap.set(task.taskId!, {
+        reject,
+        resolve,
+        workerNodeKey,
+        ...(this.emitter != null && {
+          asyncResource: new AsyncResource('poolifier:task', {
+            requireManualDestroy: true,
+            triggerAsyncId: this.emitter.asyncId,
+          }),
+        }),
       })
-    })
-    workerNode.registerWorkerEventHandler(
-      'exit',
-      this.opts.exitHandler ?? EMPTY_FUNCTION
-    )
-    workerNode.registerOnceWorkerEventHandler('exit', () => {
-      this.removeWorkerNode(workerNode)
       if (
-        this.started &&
-        !this.startingMinimumNumberOfWorkers &&
-        !this.destroying
+        this.opts.enableTasksQueue === false ||
+        (this.opts.enableTasksQueue === true &&
+          this.shallExecuteTask(workerNodeKey))
       ) {
-        this.startMinimumNumberOfWorkers(true)
+        this.executeTask(workerNodeKey, task)
+      } else {
+        this.enqueueTask(workerNodeKey, task)
       }
     })
-    const workerNodeKey = this.addWorkerNode(workerNode)
-    this.afterWorkerNodeSetup(workerNodeKey)
-    return workerNodeKey
   }
 
-  /**
-   * Creates a new, completely set up dynamic worker node.
-   * @returns New, completely set up dynamic worker node key.
-   */
-  protected createAndSetupDynamicWorkerNode (): number {
-    const workerNodeKey = this.createAndSetupWorkerNode()
-    this.registerWorkerMessageListener(workerNodeKey, message => {
-      this.checkMessageWorkerId(message)
-      const localWorkerNodeKey = this.getWorkerNodeKeyByWorkerId(
-        message.workerId
+  private isWorkerNodeBusy (workerNodeKey: number): boolean {
+    if (this.opts.enableTasksQueue === true) {
+      return (
+        this.workerNodes[workerNodeKey].usage.tasks.executing >=
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.opts.tasksQueueOptions!.concurrency!
       )
-      const workerInfo = this.getWorkerInfo(localWorkerNodeKey)
-      // Kill message received from worker
-      if (
-        isKillBehavior(KillBehaviors.HARD, message.kill) ||
-        (isKillBehavior(KillBehaviors.SOFT, message.kill) &&
-          this.isWorkerNodeIdle(localWorkerNodeKey) &&
-          workerInfo != null &&
-          !workerInfo.continuousStealing &&
-          !workerInfo.backPressureStealing)
-      ) {
-        // Flag the worker node as not ready immediately
-        this.flagWorkerNodeAsNotReady(localWorkerNodeKey)
-        this.destroyWorkerNode(localWorkerNodeKey).catch((error: unknown) => {
-          this.emitter?.emit(PoolEvents.error, error)
-        })
-      }
-    })
-    this.sendToWorker(workerNodeKey, {
-      checkActive: true,
-    })
-    if (this.taskFunctions.size > 0) {
-      for (const [taskFunctionName, taskFunctionObject] of this.taskFunctions) {
-        this.sendTaskFunctionOperationToWorker(workerNodeKey, {
-          taskFunctionOperation: 'add',
-          taskFunctionProperties: buildTaskFunctionProperties(
-            taskFunctionName,
-            taskFunctionObject
-          ),
-          taskFunction: taskFunctionObject.taskFunction.toString(),
-        }).catch((error: unknown) => {
-          this.emitter?.emit(PoolEvents.error, error)
-        })
-      }
-    }
-    const workerNode = this.workerNodes[workerNodeKey]
-    workerNode.info.dynamic = true
-    if (
-      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerReady ===
-        true ||
-      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerUsage ===
-        true
-    ) {
-      workerNode.info.ready = true
     }
-    this.initWorkerNodeUsage(workerNode)
-    this.checkAndEmitDynamicWorkerCreationEvents()
-    return workerNodeKey
+    return this.workerNodes[workerNodeKey].usage.tasks.executing > 0
   }
 
-  /**
-   * Registers a listener callback on the worker given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @param listener - The message listener callback.
-   */
-  protected abstract registerWorkerMessageListener<
-    Message extends Data | Response
-  >(
-    workerNodeKey: number,
-    listener: (message: MessageValue<Message>) => void
-  ): void
-
-  /**
-   * Registers once a listener callback on the worker given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @param listener - The message listener callback.
-   */
-  protected abstract registerOnceWorkerMessageListener<
-    Message extends Data | Response
-  >(
-    workerNodeKey: number,
-    listener: (message: MessageValue<Message>) => void
-  ): void
-
-  /**
-   * Deregisters a listener callback on the worker given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @param listener - The message listener callback.
-   */
-  protected abstract deregisterWorkerMessageListener<
-    Message extends Data | Response
-  >(
-    workerNodeKey: number,
-    listener: (message: MessageValue<Message>) => void
-  ): void
-
-  /**
-   * Method hooked up after a worker node has been newly created.
-   * Can be overridden.
-   * @param workerNodeKey - The newly created worker node key.
-   */
-  protected afterWorkerNodeSetup (workerNodeKey: number): void {
-    // Listen to worker messages.
-    this.registerWorkerMessageListener(
-      workerNodeKey,
-      this.workerMessageListener
-    )
-    // Send the startup message to worker.
-    this.sendStartupMessageToWorker(workerNodeKey)
-    // Send the statistics message to worker.
-    this.sendStatisticsMessageToWorker(workerNodeKey)
+  private isWorkerNodeIdle (workerNodeKey: number): boolean {
     if (this.opts.enableTasksQueue === true) {
-      if (this.opts.tasksQueueOptions?.taskStealing === true) {
-        this.workerNodes[workerNodeKey].on(
-          'idle',
-          this.handleWorkerNodeIdleEvent
-        )
-      }
-      if (this.opts.tasksQueueOptions?.tasksStealingOnBackPressure === true) {
-        this.workerNodes[workerNodeKey].on(
-          'backPressure',
-          this.handleWorkerNodeBackPressureEvent
-        )
-      }
+      return (
+        this.workerNodes[workerNodeKey].usage.tasks.executing === 0 &&
+        this.tasksQueueSize(workerNodeKey) === 0
+      )
+    }
+    return this.workerNodes[workerNodeKey].usage.tasks.executing === 0
+  }
+
+  private redistributeQueuedTasks (sourceWorkerNodeKey: number): void {
+    if (sourceWorkerNodeKey === -1 || this.cannotStealTask()) {
+      return
+    }
+    while (this.tasksQueueSize(sourceWorkerNodeKey) > 0) {
+      const destinationWorkerNodeKey = this.workerNodes.reduce(
+        (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
+          return sourceWorkerNodeKey !== workerNodeKey &&
+            workerNode.info.ready &&
+            workerNode.usage.tasks.queued <
+              workerNodes[minWorkerNodeKey].usage.tasks.queued
+            ? workerNodeKey
+            : minWorkerNodeKey
+        },
+        0
+      )
+      this.handleTask(
+        destinationWorkerNodeKey,
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.dequeueTask(sourceWorkerNodeKey)!
+      )
     }
   }
 
   /**
-   * Sends the startup message to worker given its worker node key.
-   * @param workerNodeKey - The worker node key.
+   * Removes the worker node from the pool worker nodes.
+   * @param workerNode - The worker node.
    */
-  protected abstract sendStartupMessageToWorker (workerNodeKey: number): void
+  private removeWorkerNode (workerNode: IWorkerNode<Worker, Data>): void {
+    const workerNodeKey = this.workerNodes.indexOf(workerNode)
+    if (workerNodeKey !== -1) {
+      this.workerNodes.splice(workerNodeKey, 1)
+      this.workerChoiceStrategiesContext?.remove(workerNodeKey)
+    }
+    this.checkAndEmitEmptyEvent()
+  }
+
+  private resetTaskSequentiallyStolenStatisticsWorkerUsage (
+    workerNodeKey: number,
+    taskName?: string
+  ): void {
+    const workerNode = this.workerNodes[workerNodeKey]
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (workerNode?.usage != null) {
+      workerNode.usage.tasks.sequentiallyStolen = 0
+    }
+    if (
+      taskName != null &&
+      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
+      workerNode.getTaskFunctionWorkerUsage(taskName) != null
+    ) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      workerNode.getTaskFunctionWorkerUsage(
+        taskName
+      )!.tasks.sequentiallyStolen = 0
+    }
+  }
+
+  private async sendKillMessageToWorker (workerNodeKey: number): Promise<void> {
+    await new Promise<void>((resolve, reject) => {
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+      if (this.workerNodes[workerNodeKey] == null) {
+        resolve()
+        return
+      }
+      const killMessageListener = (message: MessageValue<Response>): void => {
+        this.checkMessageWorkerId(message)
+        if (message.kill === 'success') {
+          resolve()
+        } else if (message.kill === 'failure') {
+          reject(
+            new Error(
+              // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              `Kill message handling failed on worker ${message.workerId?.toString()}`
+            )
+          )
+        }
+      }
+      // FIXME: should be registered only once
+      this.registerWorkerMessageListener(workerNodeKey, killMessageListener)
+      this.sendToWorker(workerNodeKey, { kill: true })
+    })
+  }
 
   /**
    * Sends the statistics message to worker given its worker node key.
@@ -1803,67 +1415,204 @@ export abstract class AbstractPool<
   private sendStatisticsMessageToWorker (workerNodeKey: number): void {
     this.sendToWorker(workerNodeKey, {
       statistics: {
-        runTime:
-          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-            .runTime.aggregate ?? false,
         elu:
           this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
             .elu.aggregate ?? false,
+        runTime:
+          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+            .runTime.aggregate ?? false,
       },
     })
   }
 
-  private cannotStealTask (): boolean {
-    return this.workerNodes.length <= 1 || this.info.queuedTasks === 0
+  private async sendTaskFunctionOperationToWorker (
+    workerNodeKey: number,
+    message: MessageValue<Data>
+  ): Promise<boolean> {
+    return await new Promise<boolean>((resolve, reject) => {
+      const taskFunctionOperationListener = (
+        message: MessageValue<Response>
+      ): void => {
+        this.checkMessageWorkerId(message)
+        const workerId = this.getWorkerInfo(workerNodeKey)?.id
+        if (
+          message.taskFunctionOperationStatus != null &&
+          message.workerId === workerId
+        ) {
+          if (message.taskFunctionOperationStatus) {
+            resolve(true)
+          } else {
+            reject(
+              new Error(
+                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                `Task function operation '${message.taskFunctionOperation?.toString()}' failed on worker ${message.workerId?.toString()} with error: '${
+                  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  message.workerError?.message
+                }'`
+              )
+            )
+          }
+          this.deregisterWorkerMessageListener(
+            this.getWorkerNodeKeyByWorkerId(message.workerId),
+            taskFunctionOperationListener
+          )
+        }
+      }
+      this.registerWorkerMessageListener(
+        workerNodeKey,
+        taskFunctionOperationListener
+      )
+      this.sendToWorker(workerNodeKey, message)
+    })
   }
 
-  private handleTask (workerNodeKey: number, task: Task<Data>): void {
-    if (this.shallExecuteTask(workerNodeKey)) {
-      this.executeTask(workerNodeKey, task)
-    } else {
-      this.enqueueTask(workerNodeKey, task)
+  private async sendTaskFunctionOperationToWorkers (
+    message: MessageValue<Data>
+  ): Promise<boolean> {
+    return await new Promise<boolean>((resolve, reject) => {
+      const responsesReceived = new Array<MessageValue<Response>>()
+      const taskFunctionOperationsListener = (
+        message: MessageValue<Response>
+      ): void => {
+        this.checkMessageWorkerId(message)
+        if (message.taskFunctionOperationStatus != null) {
+          responsesReceived.push(message)
+          if (responsesReceived.length === this.workerNodes.length) {
+            if (
+              responsesReceived.every(
+                message => message.taskFunctionOperationStatus === true
+              )
+            ) {
+              resolve(true)
+            } else if (
+              responsesReceived.some(
+                message => message.taskFunctionOperationStatus === false
+              )
+            ) {
+              const errorResponse = responsesReceived.find(
+                response => response.taskFunctionOperationStatus === false
+              )
+              reject(
+                new Error(
+                  `Task function operation '${
+                    message.taskFunctionOperation as string
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                  }' failed on worker ${errorResponse?.workerId?.toString()} with error: '${
+                    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+                    errorResponse?.workerError?.message
+                  }'`
+                )
+              )
+            }
+            this.deregisterWorkerMessageListener(
+              this.getWorkerNodeKeyByWorkerId(message.workerId),
+              taskFunctionOperationsListener
+            )
+          }
+        }
+      }
+      for (const workerNodeKey of this.workerNodes.keys()) {
+        this.registerWorkerMessageListener(
+          workerNodeKey,
+          taskFunctionOperationsListener
+        )
+        this.sendToWorker(workerNodeKey, message)
+      }
+    })
+  }
+
+  private setTasksQueuePriority (workerNodeKey: number): void {
+    this.workerNodes[workerNodeKey].setTasksQueuePriority(
+      this.getTasksQueuePriority()
+    )
+  }
+
+  private setTasksQueueSize (size: number): void {
+    for (const workerNode of this.workerNodes) {
+      workerNode.tasksQueueBackPressureSize = size
     }
   }
 
-  private redistributeQueuedTasks (sourceWorkerNodeKey: number): void {
-    if (sourceWorkerNodeKey === -1 || this.cannotStealTask()) {
+  private setTasksStealingOnBackPressure (): void {
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.workerNodes[workerNodeKey].on(
+        'backPressure',
+        this.handleWorkerNodeBackPressureEvent
+      )
+    }
+  }
+
+  private setTaskStealing (): void {
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.workerNodes[workerNodeKey].on('idle', this.handleWorkerNodeIdleEvent)
+    }
+  }
+
+  private shallExecuteTask (workerNodeKey: number): boolean {
+    return (
+      this.tasksQueueSize(workerNodeKey) === 0 &&
+      this.workerNodes[workerNodeKey].usage.tasks.executing <
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.opts.tasksQueueOptions!.concurrency!
+    )
+  }
+
+  /**
+   * Whether the worker node shall update its task function worker usage or not.
+   * @param workerNodeKey - The worker node key.
+   * @returns `true` if the worker node shall update its task function worker usage, `false` otherwise.
+   */
+  private shallUpdateTaskFunctionWorkerUsage (workerNodeKey: number): boolean {
+    const workerInfo = this.getWorkerInfo(workerNodeKey)
+    return (
+      workerInfo != null &&
+      Array.isArray(workerInfo.taskFunctionsProperties) &&
+      workerInfo.taskFunctionsProperties.length > 2
+    )
+  }
+
+  /**
+   * Starts the minimum number of workers.
+   * @param initWorkerNodeUsage - Whether to initialize the worker node usage or not. @defaultValue false
+   */
+  private startMinimumNumberOfWorkers (initWorkerNodeUsage = false): void {
+    if (this.minimumNumberOfWorkers === 0) {
       return
     }
-    while (this.tasksQueueSize(sourceWorkerNodeKey) > 0) {
-      const destinationWorkerNodeKey = this.workerNodes.reduce(
-        (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
-          return sourceWorkerNodeKey !== workerNodeKey &&
-            workerNode.info.ready &&
-            workerNode.usage.tasks.queued <
-              workerNodes[minWorkerNodeKey].usage.tasks.queued
-            ? workerNodeKey
-            : minWorkerNodeKey
-        },
+    this.startingMinimumNumberOfWorkers = true
+    while (
+      this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          !workerNode.info.dynamic ? accumulator + 1 : accumulator,
         0
-      )
-      this.handleTask(
-        destinationWorkerNodeKey,
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.dequeueTask(sourceWorkerNodeKey)!
-      )
+      ) < this.minimumNumberOfWorkers
+    ) {
+      const workerNodeKey = this.createAndSetupWorkerNode()
+      initWorkerNodeUsage &&
+        this.initWorkerNodeUsage(this.workerNodes[workerNodeKey])
     }
+    this.startingMinimumNumberOfWorkers = false
   }
 
-  private updateTaskStolenStatisticsWorkerUsage (
-    workerNodeKey: number,
-    taskName: string
-  ): void {
-    const workerNode = this.workerNodes[workerNodeKey]
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (workerNode?.usage != null) {
-      ++workerNode.usage.tasks.stolen
+  private tasksQueueSize (workerNodeKey: number): number {
+    return this.workerNodes[workerNodeKey].tasksQueueSize()
+  }
+
+  private unsetTasksStealingOnBackPressure (): void {
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.workerNodes[workerNodeKey].off(
+        'backPressure',
+        this.handleWorkerNodeBackPressureEvent
+      )
     }
-    if (
-      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
-      workerNode.getTaskFunctionWorkerUsage(taskName) != null
-    ) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ++workerNode.getTaskFunctionWorkerUsage(taskName)!.tasks.stolen
+  }
+
+  private unsetTaskStealing (): void {
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.workerNodes[workerNodeKey].off(
+        'idle',
+        this.handleWorkerNodeIdleEvent
+      )
     }
   }
 
@@ -1898,441 +1647,699 @@ export abstract class AbstractPool<
     }
   }
 
-  private resetTaskSequentiallyStolenStatisticsWorkerUsage (
+  private updateTaskStolenStatisticsWorkerUsage (
     workerNodeKey: number,
-    taskName?: string
+    taskName: string
   ): void {
     const workerNode = this.workerNodes[workerNodeKey]
     // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
     if (workerNode?.usage != null) {
-      workerNode.usage.tasks.sequentiallyStolen = 0
+      ++workerNode.usage.tasks.stolen
     }
     if (
-      taskName != null &&
       this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
       workerNode.getTaskFunctionWorkerUsage(taskName) != null
     ) {
       // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      workerNode.getTaskFunctionWorkerUsage(
-        taskName
-      )!.tasks.sequentiallyStolen = 0
+      ++workerNode.getTaskFunctionWorkerUsage(taskName)!.tasks.stolen
     }
   }
 
-  private readonly stealTask = (
-    sourceWorkerNode: IWorkerNode<Worker, Data>,
-    destinationWorkerNodeKey: number
-  ): Task<Data> | undefined => {
-    const destinationWorkerInfo = this.getWorkerInfo(destinationWorkerNodeKey)
-    if (destinationWorkerInfo == null) {
-      throw new Error(
-        `Worker node with key '${destinationWorkerNodeKey.toString()}' not found in pool`
-      )
+  /** @inheritDoc */
+  public async addTaskFunction (
+    name: string,
+    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
+  ): Promise<boolean> {
+    if (typeof name !== 'string') {
+      throw new TypeError('name argument must be a string')
     }
-    // Avoid cross and cascading task stealing. Could be smarter by checking stealing/stolen worker ids pair.
-    if (
-      !sourceWorkerNode.info.ready ||
-      sourceWorkerNode.info.stolen ||
-      sourceWorkerNode.info.stealing ||
-      !destinationWorkerInfo.ready ||
-      destinationWorkerInfo.stolen ||
-      destinationWorkerInfo.stealing
-    ) {
-      return
+    if (typeof name === 'string' && name.trim().length === 0) {
+      throw new TypeError('name argument must not be an empty string')
     }
-    destinationWorkerInfo.stealing = true
-    sourceWorkerNode.info.stolen = true
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const stolenTask = sourceWorkerNode.dequeueLastPrioritizedTask()!
-    sourceWorkerNode.info.stolen = false
-    destinationWorkerInfo.stealing = false
-    this.handleTask(destinationWorkerNodeKey, stolenTask)
-    this.updateTaskStolenStatisticsWorkerUsage(
-      destinationWorkerNodeKey,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      stolenTask.name!
+    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')
+    }
+    checkValidPriority(fn.priority)
+    checkValidWorkerChoiceStrategy(fn.strategy)
+    const opResult = await this.sendTaskFunctionOperationToWorkers({
+      taskFunction: fn.taskFunction.toString(),
+      taskFunctionOperation: 'add',
+      taskFunctionProperties: buildTaskFunctionProperties(name, fn),
+    })
+    this.taskFunctions.set(name, fn)
+    this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
+      this.getWorkerChoiceStrategies()
     )
-    return stolenTask
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.sendStatisticsMessageToWorker(workerNodeKey)
+    }
+    return opResult
   }
 
-  private readonly handleWorkerNodeIdleEvent = (
-    eventDetail: WorkerNodeEventDetail,
-    previousStolenTask?: Task<Data>
-  ): void => {
-    const { workerNodeKey } = eventDetail
-    if (workerNodeKey == null) {
-      throw new Error(
-        "WorkerNode event detail 'workerNodeKey' property must be defined"
-      )
+  /** @inheritDoc */
+  public async destroy (): Promise<void> {
+    if (!this.started) {
+      throw new Error('Cannot destroy an already destroyed pool')
     }
-    const workerNodeInfo = this.getWorkerInfo(workerNodeKey)
-    if (workerNodeInfo == null) {
-      throw new Error(
-        `Worker node with key '${workerNodeKey.toString()}' not found in pool`
-      )
+    if (this.starting) {
+      throw new Error('Cannot destroy an starting pool')
     }
-    if (
-      !workerNodeInfo.continuousStealing &&
-      (this.cannotStealTask() ||
-        (this.info.stealingWorkerNodes ?? 0) >
-          Math.round(
-            this.workerNodes.length *
-              // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-              this.opts.tasksQueueOptions!.tasksStealingRatio!
-          ))
-    ) {
-      return
+    if (this.destroying) {
+      throw new Error('Cannot destroy an already destroying pool')
     }
-    const workerNodeTasksUsage = this.workerNodes[workerNodeKey].usage.tasks
-    if (
-      workerNodeInfo.continuousStealing &&
-      (workerNodeTasksUsage.executing > 0 ||
-        this.tasksQueueSize(workerNodeKey) > 0)
-    ) {
-      workerNodeInfo.continuousStealing = false
-      if (workerNodeTasksUsage.sequentiallyStolen > 0) {
-        this.resetTaskSequentiallyStolenStatisticsWorkerUsage(
-          workerNodeKey,
-          previousStolenTask?.name
-        )
+    this.destroying = true
+    await Promise.all(
+      this.workerNodes.map(async (_, workerNodeKey) => {
+        await this.destroyWorkerNode(workerNodeKey)
+      })
+    )
+    this.emitter?.emit(PoolEvents.destroy, this.info)
+    this.emitter?.emitDestroy()
+    this.readyEventEmitted = false
+    delete this.startTimestamp
+    this.destroying = false
+    this.started = false
+  }
+
+  /** @inheritDoc */
+  public enableTasksQueue (
+    enable: boolean,
+    tasksQueueOptions?: TasksQueueOptions
+  ): void {
+    if (this.opts.enableTasksQueue === true && !enable) {
+      this.unsetTaskStealing()
+      this.unsetTasksStealingOnBackPressure()
+      this.flushTasksQueues()
+    }
+    this.opts.enableTasksQueue = enable
+    this.setTasksQueueOptions(tasksQueueOptions)
+  }
+
+  /** @inheritDoc */
+  public async execute (
+    data?: Data,
+    name?: string,
+    transferList?: readonly TransferListItem[]
+  ): Promise<Response> {
+    if (!this.started) {
+      throw new Error('Cannot execute a task on not started pool')
+    }
+    if (this.destroying) {
+      throw new Error('Cannot execute a task on destroying pool')
+    }
+    if (name != null && typeof name !== 'string') {
+      throw new TypeError('name argument must be a string')
+    }
+    if (name != null && typeof name === 'string' && name.trim().length === 0) {
+      throw new TypeError('name argument must not be an empty string')
+    }
+    if (transferList != null && !Array.isArray(transferList)) {
+      throw new TypeError('transferList argument must be an array')
+    }
+    return await this.internalExecute(data, name, transferList)
+  }
+
+  /** @inheritDoc */
+  public hasTaskFunction (name: string): boolean {
+    return this.listTaskFunctionsProperties().some(
+      taskFunctionProperties => taskFunctionProperties.name === name
+    )
+  }
+
+  /** @inheritDoc */
+  public listTaskFunctionsProperties (): TaskFunctionProperties[] {
+    for (const workerNode of this.workerNodes) {
+      if (
+        Array.isArray(workerNode.info.taskFunctionsProperties) &&
+        workerNode.info.taskFunctionsProperties.length > 0
+      ) {
+        return workerNode.info.taskFunctionsProperties
       }
-      return
     }
-    workerNodeInfo.continuousStealing = true
-    const stolenTask = this.workerNodeStealTask(workerNodeKey)
-    this.updateTaskSequentiallyStolenStatisticsWorkerUsage(
-      workerNodeKey,
-      stolenTask?.name,
-      previousStolenTask?.name
-    )
-    sleep(exponentialDelay(workerNodeTasksUsage.sequentiallyStolen))
-      .then(() => {
-        this.handleWorkerNodeIdleEvent(eventDetail, stolenTask)
-        return undefined
-      })
-      .catch((error: unknown) => {
-        this.emitter?.emit(PoolEvents.error, error)
-      })
-  }
-
-  private readonly workerNodeStealTask = (
-    workerNodeKey: number
-  ): Task<Data> | undefined => {
-    const workerNodes = this.workerNodes
-      .slice()
-      .sort(
-        (workerNodeA, workerNodeB) =>
-          workerNodeB.usage.tasks.queued - workerNodeA.usage.tasks.queued
+    return []
+  }
+
+  /** @inheritDoc */
+  public async mapExecute (
+    data: Iterable<Data>,
+    name?: string,
+    transferList?: readonly TransferListItem[]
+  ): Promise<Response[]> {
+    if (!this.started) {
+      throw new Error('Cannot execute task(s) on not started pool')
+    }
+    if (this.destroying) {
+      throw new Error('Cannot execute task(s) on destroying pool')
+    }
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (data == null) {
+      throw new TypeError('data argument must be a defined iterable')
+    }
+    if (typeof data[Symbol.iterator] !== 'function') {
+      throw new TypeError('data argument must be an iterable')
+    }
+    if (name != null && typeof name !== 'string') {
+      throw new TypeError('name argument must be a string')
+    }
+    if (name != null && typeof name === 'string' && name.trim().length === 0) {
+      throw new TypeError('name argument must not be an empty string')
+    }
+    if (transferList != null && !Array.isArray(transferList)) {
+      throw new TypeError('transferList argument must be an array')
+    }
+    if (!Array.isArray(data)) {
+      data = [...data]
+    }
+    return await Promise.all(
+      (data as Data[]).map(data =>
+        this.internalExecute(data, name, transferList)
       )
-    const sourceWorkerNode = workerNodes.find(
-      (sourceWorkerNode, sourceWorkerNodeKey) =>
-        sourceWorkerNodeKey !== workerNodeKey &&
-        sourceWorkerNode.usage.tasks.queued > 0
     )
-    if (sourceWorkerNode != null) {
-      return this.stealTask(sourceWorkerNode, workerNodeKey)
-    }
   }
 
-  private readonly handleWorkerNodeBackPressureEvent = (
-    eventDetail: WorkerNodeEventDetail
-  ): void => {
-    if (
-      this.cannotStealTask() ||
-      this.hasBackPressure() ||
-      (this.info.stealingWorkerNodes ?? 0) >
-        Math.round(
-          this.workerNodes.length *
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            this.opts.tasksQueueOptions!.tasksStealingRatio!
-        )
-    ) {
-      return
+  /** @inheritDoc */
+  public async removeTaskFunction (name: string): Promise<boolean> {
+    if (!this.taskFunctions.has(name)) {
+      throw new Error(
+        'Cannot remove a task function not handled on the pool side'
+      )
     }
-    const sizeOffset = 1
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    if (this.opts.tasksQueueOptions!.size! <= sizeOffset) {
-      return
+    const opResult = await this.sendTaskFunctionOperationToWorkers({
+      taskFunctionOperation: 'remove',
+      taskFunctionProperties: buildTaskFunctionProperties(
+        name,
+        this.taskFunctions.get(name)
+      ),
+    })
+    for (const workerNode of this.workerNodes) {
+      workerNode.deleteTaskFunctionWorkerUsage(name)
     }
-    const { workerId } = eventDetail
-    const sourceWorkerNode =
-      this.workerNodes[this.getWorkerNodeKeyByWorkerId(workerId)]
-    const workerNodes = this.workerNodes
-      .slice()
-      .sort(
-        (workerNodeA, workerNodeB) =>
-          workerNodeA.usage.tasks.queued - workerNodeB.usage.tasks.queued
-      )
-    for (const [workerNodeKey, workerNode] of workerNodes.entries()) {
-      if (
-        sourceWorkerNode.usage.tasks.queued > 0 &&
-        workerNode.info.id !== workerId &&
-        workerNode.usage.tasks.queued <
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.opts.tasksQueueOptions!.size! - sizeOffset
-      ) {
-        if (workerNode.info.backPressureStealing) {
-          continue
-        }
-        workerNode.info.backPressureStealing = true
-        this.stealTask(sourceWorkerNode, workerNodeKey)
-        workerNode.info.backPressureStealing = false
-      }
+    this.taskFunctions.delete(name)
+    this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
+      this.getWorkerChoiceStrategies()
+    )
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.sendStatisticsMessageToWorker(workerNodeKey)
     }
+    return opResult
   }
 
-  private setTasksQueuePriority (workerNodeKey: number): void {
-    this.workerNodes[workerNodeKey].setTasksQueuePriority(
-      this.getTasksQueuePriority()
-    )
+  /** @inheritDoc */
+  public async setDefaultTaskFunction (name: string): Promise<boolean> {
+    return await this.sendTaskFunctionOperationToWorkers({
+      taskFunctionOperation: 'default',
+      taskFunctionProperties: buildTaskFunctionProperties(
+        name,
+        this.taskFunctions.get(name)
+      ),
+    })
   }
 
-  /**
-   * This method is the message listener registered on each worker.
-   * @param message - The message received from the worker.
-   */
-  protected readonly workerMessageListener = (
-    message: MessageValue<Response>
-  ): void => {
-    this.checkMessageWorkerId(message)
-    const { workerId, ready, taskId, taskFunctionsProperties } = message
-    if (ready != null && taskFunctionsProperties != null) {
-      // Worker ready response received from worker
-      this.handleWorkerReadyResponse(message)
-    } else if (taskFunctionsProperties != null) {
-      // Task function properties message received from worker
-      const workerNodeKey = this.getWorkerNodeKeyByWorkerId(workerId)
-      const workerInfo = this.getWorkerInfo(workerNodeKey)
-      if (workerInfo != null) {
-        workerInfo.taskFunctionsProperties = taskFunctionsProperties
-        this.sendStatisticsMessageToWorker(workerNodeKey)
-        this.setTasksQueuePriority(workerNodeKey)
+  /** @inheritDoc */
+  public setTasksQueueOptions (
+    tasksQueueOptions: TasksQueueOptions | undefined
+  ): void {
+    if (this.opts.enableTasksQueue === true) {
+      checkValidTasksQueueOptions(tasksQueueOptions)
+      this.opts.tasksQueueOptions =
+        this.buildTasksQueueOptions(tasksQueueOptions)
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.setTasksQueueSize(this.opts.tasksQueueOptions.size!)
+      if (this.opts.tasksQueueOptions.taskStealing === true) {
+        this.unsetTaskStealing()
+        this.setTaskStealing()
+      } else {
+        this.unsetTaskStealing()
       }
-    } else if (taskId != null) {
-      // Task execution response received from worker
-      this.handleTaskExecutionResponse(message)
+      if (this.opts.tasksQueueOptions.tasksStealingOnBackPressure === true) {
+        this.unsetTasksStealingOnBackPressure()
+        this.setTasksStealingOnBackPressure()
+      } else {
+        this.unsetTasksStealingOnBackPressure()
+      }
+    } else if (this.opts.tasksQueueOptions != null) {
+      delete this.opts.tasksQueueOptions
     }
   }
 
-  private checkAndEmitReadyEvent (): void {
-    if (!this.readyEventEmitted && this.ready) {
-      this.emitter?.emit(PoolEvents.ready, this.info)
-      this.readyEventEmitted = true
+  /** @inheritDoc */
+  public setWorkerChoiceStrategy (
+    workerChoiceStrategy: WorkerChoiceStrategy,
+    workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions
+  ): void {
+    let requireSync = false
+    checkValidWorkerChoiceStrategy(workerChoiceStrategy)
+    if (workerChoiceStrategyOptions != null) {
+      requireSync = !this.setWorkerChoiceStrategyOptions(
+        workerChoiceStrategyOptions
+      )
     }
-  }
-
-  private handleWorkerReadyResponse (message: MessageValue<Response>): void {
-    const { workerId, ready, taskFunctionsProperties } = message
-    if (ready == null || !ready) {
-      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-      throw new Error(`Worker ${workerId?.toString()} failed to initialize`)
+    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.getWorkerChoiceStrategies(),
+        this.opts.workerChoiceStrategyOptions
+      )
+      for (const workerNodeKey of this.workerNodes.keys()) {
+        this.sendStatisticsMessageToWorker(workerNodeKey)
+      }
     }
-    const workerNodeKey = this.getWorkerNodeKeyByWorkerId(workerId)
-    const workerNode = this.workerNodes[workerNodeKey]
-    workerNode.info.ready = ready
-    workerNode.info.taskFunctionsProperties = taskFunctionsProperties
-    this.sendStatisticsMessageToWorker(workerNodeKey)
-    this.setTasksQueuePriority(workerNodeKey)
-    this.checkAndEmitReadyEvent()
   }
 
-  private handleTaskExecutionResponse (message: MessageValue<Response>): void {
-    const { taskId, workerError, data } = message
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const promiseResponse = this.promiseResponseMap.get(taskId!)
-    if (promiseResponse != null) {
-      const { resolve, reject, workerNodeKey, asyncResource } = promiseResponse
-      const workerNode = this.workerNodes[workerNodeKey]
-      if (workerError != null) {
-        this.emitter?.emit(PoolEvents.taskError, workerError)
-        asyncResource != null
-          ? asyncResource.runInAsyncScope(
-            reject,
-            this.emitter,
-            workerError.message
-          )
-          : reject(workerError.message)
-      } else {
-        asyncResource != null
-          ? asyncResource.runInAsyncScope(resolve, this.emitter, data)
-          : resolve(data as Response)
+  /** @inheritDoc */
+  public setWorkerChoiceStrategyOptions (
+    workerChoiceStrategyOptions: undefined | WorkerChoiceStrategyOptions
+  ): boolean {
+    this.checkValidWorkerChoiceStrategyOptions(workerChoiceStrategyOptions)
+    if (workerChoiceStrategyOptions != null) {
+      this.opts.workerChoiceStrategyOptions = {
+        ...this.opts.workerChoiceStrategyOptions,
+        ...workerChoiceStrategyOptions,
       }
-      asyncResource?.emitDestroy()
-      this.afterTaskExecutionHook(workerNodeKey, message)
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.promiseResponseMap.delete(taskId!)
-      if (this.opts.enableTasksQueue === true && !this.destroying) {
-        const workerNodeTasksUsage = workerNode.usage.tasks
-        if (
-          this.tasksQueueSize(workerNodeKey) > 0 &&
-          workerNodeTasksUsage.executing <
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            this.opts.tasksQueueOptions!.concurrency!
-        ) {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.executeTask(workerNodeKey, this.dequeueTask(workerNodeKey)!)
-        }
-        if (this.isWorkerNodeIdle(workerNodeKey)) {
-          workerNode.emit('idle', {
-            workerNodeKey,
-          })
-        }
+      this.workerChoiceStrategiesContext?.setOptions(
+        this.opts.workerChoiceStrategyOptions
+      )
+      this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
+        this.getWorkerChoiceStrategies(),
+        this.opts.workerChoiceStrategyOptions
+      )
+      for (const workerNodeKey of this.workerNodes.keys()) {
+        this.sendStatisticsMessageToWorker(workerNodeKey)
       }
-      // FIXME: cannot be theoretically undefined. Schedule in the next tick to avoid race conditions?
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-      workerNode?.emit('taskFinished', taskId)
+      return true
     }
+    return false
   }
 
-  private checkAndEmitTaskExecutionEvents (): void {
-    if (this.busy) {
-      this.emitter?.emit(PoolEvents.busy, this.info)
+  /** @inheritdoc */
+  public start (): void {
+    if (this.started) {
+      throw new Error('Cannot start an already started pool')
+    }
+    if (this.starting) {
+      throw new Error('Cannot start an already starting pool')
+    }
+    if (this.destroying) {
+      throw new Error('Cannot start a destroying pool')
     }
+    this.starting = true
+    this.startMinimumNumberOfWorkers()
+    this.startTimestamp = performance.now()
+    this.starting = false
+    this.started = true
   }
 
-  private checkAndEmitTaskQueuingEvents (): void {
-    if (this.hasBackPressure()) {
-      this.emitter?.emit(PoolEvents.backPressure, this.info)
+  /**
+   * Whether the pool is busy or not.
+   * @returns The pool busyness boolean status.
+   */
+  protected abstract get busy (): boolean
+
+  /**
+   * Whether the pool is empty or not.
+   * @returns The pool emptiness boolean status.
+   */
+  protected get empty (): boolean {
+    return this.minimumNumberOfWorkers === 0 && this.workerNodes.length === 0
+  }
+
+  /**
+   * Whether the pool is full or not.
+   * @returns The pool fullness boolean status.
+   */
+  protected get full (): boolean {
+    return (
+      this.workerNodes.length >=
+      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
+    )
+  }
+
+  /** @inheritDoc */
+  public get info (): PoolInfo {
+    return {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      defaultStrategy: this.opts.workerChoiceStrategy!,
+      maxSize: this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers,
+      minSize: this.minimumNumberOfWorkers,
+      ready: this.ready,
+      started: this.started,
+      strategyRetries: this.workerChoiceStrategiesContext?.retriesCount ?? 0,
+      type: this.type,
+      version,
+      worker: this.worker,
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .runTime.aggregate === true &&
+        this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+          .waitTime.aggregate && {
+        utilization: round(this.utilization),
+      }),
+      busyWorkerNodes: this.workerNodes.reduce(
+        (accumulator, _, workerNodeKey) =>
+          this.isWorkerNodeBusy(workerNodeKey) ? accumulator + 1 : accumulator,
+        0
+      ),
+      idleWorkerNodes: this.workerNodes.reduce(
+        (accumulator, _, workerNodeKey) =>
+          this.isWorkerNodeIdle(workerNodeKey) ? accumulator + 1 : accumulator,
+        0
+      ),
+      workerNodes: this.workerNodes.length,
+      ...(this.opts.enableTasksQueue === true && {
+        stealingWorkerNodes: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            workerNode.info.continuousStealing ||
+            workerNode.info.backPressureStealing
+              ? accumulator + 1
+              : accumulator,
+          0
+        ),
+      }),
+      ...(this.opts.enableTasksQueue === true && {
+        backPressureWorkerNodes: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            workerNode.info.backPressure ? accumulator + 1 : accumulator,
+          0
+        ),
+      }),
+      executedTasks: this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          accumulator + workerNode.usage.tasks.executed,
+        0
+      ),
+      executingTasks: this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          accumulator + workerNode.usage.tasks.executing,
+        0
+      ),
+      ...(this.opts.enableTasksQueue === true && {
+        queuedTasks: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            accumulator + workerNode.usage.tasks.queued,
+          0
+        ),
+      }),
+      ...(this.opts.enableTasksQueue === true && {
+        maxQueuedTasks: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            accumulator + (workerNode.usage.tasks.maxQueued ?? 0),
+          0
+        ),
+      }),
+      ...(this.opts.enableTasksQueue === true && {
+        backPressure: this.hasBackPressure(),
+      }),
+      ...(this.opts.enableTasksQueue === true && {
+        stolenTasks: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            accumulator + workerNode.usage.tasks.stolen,
+          0
+        ),
+      }),
+      failedTasks: this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          accumulator + workerNode.usage.tasks.failed,
+        0
+      ),
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .runTime.aggregate === true && {
+        runTime: {
+          maximum: round(
+            max(
+              ...this.workerNodes.map(
+                workerNode =>
+                  workerNode.usage.runTime.maximum ?? Number.NEGATIVE_INFINITY
+              )
+            )
+          ),
+          minimum: round(
+            min(
+              ...this.workerNodes.map(
+                workerNode =>
+                  workerNode.usage.runTime.minimum ?? Number.POSITIVE_INFINITY
+              )
+            )
+          ),
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+            .runTime.average && {
+            average: round(
+              average(
+                this.workerNodes.reduce<number[]>(
+                  (accumulator, workerNode) =>
+                    accumulator.concat(
+                      workerNode.usage.runTime.history.toArray()
+                    ),
+                  []
+                )
+              )
+            ),
+          }),
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+            .runTime.median && {
+            median: round(
+              median(
+                this.workerNodes.reduce<number[]>(
+                  (accumulator, workerNode) =>
+                    accumulator.concat(
+                      workerNode.usage.runTime.history.toArray()
+                    ),
+                  []
+                )
+              )
+            ),
+          }),
+        },
+      }),
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .waitTime.aggregate === true && {
+        waitTime: {
+          maximum: round(
+            max(
+              ...this.workerNodes.map(
+                workerNode =>
+                  workerNode.usage.waitTime.maximum ?? Number.NEGATIVE_INFINITY
+              )
+            )
+          ),
+          minimum: round(
+            min(
+              ...this.workerNodes.map(
+                workerNode =>
+                  workerNode.usage.waitTime.minimum ?? Number.POSITIVE_INFINITY
+              )
+            )
+          ),
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+            .waitTime.average && {
+            average: round(
+              average(
+                this.workerNodes.reduce<number[]>(
+                  (accumulator, workerNode) =>
+                    accumulator.concat(
+                      workerNode.usage.waitTime.history.toArray()
+                    ),
+                  []
+                )
+              )
+            ),
+          }),
+          ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+            .waitTime.median && {
+            median: round(
+              median(
+                this.workerNodes.reduce<number[]>(
+                  (accumulator, workerNode) =>
+                    accumulator.concat(
+                      workerNode.usage.waitTime.history.toArray()
+                    ),
+                  []
+                )
+              )
+            ),
+          }),
+        },
+      }),
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .elu.aggregate === true && {
+        elu: {
+          active: {
+            maximum: round(
+              max(
+                ...this.workerNodes.map(
+                  workerNode =>
+                    workerNode.usage.elu.active.maximum ??
+                    Number.NEGATIVE_INFINITY
+                )
+              )
+            ),
+            minimum: round(
+              min(
+                ...this.workerNodes.map(
+                  workerNode =>
+                    workerNode.usage.elu.active.minimum ??
+                    Number.POSITIVE_INFINITY
+                )
+              )
+            ),
+            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+              .elu.average && {
+              average: round(
+                average(
+                  this.workerNodes.reduce<number[]>(
+                    (accumulator, workerNode) =>
+                      accumulator.concat(
+                        workerNode.usage.elu.active.history.toArray()
+                      ),
+                    []
+                  )
+                )
+              ),
+            }),
+            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+              .elu.median && {
+              median: round(
+                median(
+                  this.workerNodes.reduce<number[]>(
+                    (accumulator, workerNode) =>
+                      accumulator.concat(
+                        workerNode.usage.elu.active.history.toArray()
+                      ),
+                    []
+                  )
+                )
+              ),
+            }),
+          },
+          idle: {
+            maximum: round(
+              max(
+                ...this.workerNodes.map(
+                  workerNode =>
+                    workerNode.usage.elu.idle.maximum ??
+                    Number.NEGATIVE_INFINITY
+                )
+              )
+            ),
+            minimum: round(
+              min(
+                ...this.workerNodes.map(
+                  workerNode =>
+                    workerNode.usage.elu.idle.minimum ??
+                    Number.POSITIVE_INFINITY
+                )
+              )
+            ),
+            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+              .elu.average && {
+              average: round(
+                average(
+                  this.workerNodes.reduce<number[]>(
+                    (accumulator, workerNode) =>
+                      accumulator.concat(
+                        workerNode.usage.elu.idle.history.toArray()
+                      ),
+                    []
+                  )
+                )
+              ),
+            }),
+            ...(this.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
+              .elu.median && {
+              median: round(
+                median(
+                  this.workerNodes.reduce<number[]>(
+                    (accumulator, workerNode) =>
+                      accumulator.concat(
+                        workerNode.usage.elu.idle.history.toArray()
+                      ),
+                    []
+                  )
+                )
+              ),
+            }),
+          },
+          utilization: {
+            average: round(
+              average(
+                this.workerNodes.map(
+                  workerNode => workerNode.usage.elu.utilization ?? 0
+                )
+              )
+            ),
+            median: round(
+              median(
+                this.workerNodes.map(
+                  workerNode => workerNode.usage.elu.utilization ?? 0
+                )
+              )
+            ),
+          },
+        },
+      }),
     }
   }
 
   /**
-   * Emits dynamic worker creation events.
-   */
-  protected abstract checkAndEmitDynamicWorkerCreationEvents (): void
-
-  /**
-   * Gets the worker information given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @returns The worker information.
-   */
-  protected getWorkerInfo (workerNodeKey: number): WorkerInfo | undefined {
-    return this.workerNodes[workerNodeKey]?.info
-  }
-
-  private getTasksQueuePriority (): boolean {
-    return this.listTaskFunctionsProperties().some(
-      taskFunctionProperties => taskFunctionProperties.priority != null
-    )
-  }
-
-  /**
-   * Creates a worker node.
-   * @returns The created worker node.
+   * Whether the pool is ready or not.
+   * @returns The pool readiness boolean status.
    */
-  private createWorkerNode (): IWorkerNode<Worker, Data> {
-    const workerNode = new WorkerNode<Worker, Data>(
-      this.worker,
-      this.filePath,
-      {
-        env: this.opts.env,
-        workerOptions: this.opts.workerOptions,
-        tasksQueueBackPressureSize:
-          this.opts.tasksQueueOptions?.size ??
-          getDefaultTasksQueueOptions(
-            this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
-          ).size,
-        tasksQueueBucketSize: defaultBucketSize,
-        tasksQueuePriority: this.getTasksQueuePriority(),
-      }
-    )
-    // Flag the worker node as ready at pool startup.
-    if (this.starting) {
-      workerNode.info.ready = true
+  private get ready (): boolean {
+    if (this.empty) {
+      return false
     }
-    return workerNode
+    return (
+      this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          !workerNode.info.dynamic && workerNode.info.ready
+            ? accumulator + 1
+            : accumulator,
+        0
+      ) >= this.minimumNumberOfWorkers
+    )
   }
 
   /**
-   * Adds the given worker node in the pool worker nodes.
-   * @param workerNode - The worker node.
-   * @returns The added worker node key.
-   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the added worker node is not found.
+   * The pool type.
+   *
+   * If it is `'dynamic'`, it provides the `max` property.
    */
-  private addWorkerNode (workerNode: IWorkerNode<Worker, Data>): number {
-    this.workerNodes.push(workerNode)
-    const workerNodeKey = this.workerNodes.indexOf(workerNode)
-    if (workerNodeKey === -1) {
-      throw new Error('Worker added not found in worker nodes')
-    }
-    return workerNodeKey
-  }
-
-  private checkAndEmitEmptyEvent (): void {
-    if (this.empty) {
-      this.emitter?.emit(PoolEvents.empty, this.info)
-      this.readyEventEmitted = false
-    }
-  }
+  protected abstract get type (): PoolType
 
   /**
-   * Removes the worker node from the pool worker nodes.
-   * @param workerNode - The worker node.
+   * The approximate pool utilization.
+   * @returns The pool utilization.
    */
-  private removeWorkerNode (workerNode: IWorkerNode<Worker, Data>): void {
-    const workerNodeKey = this.workerNodes.indexOf(workerNode)
-    if (workerNodeKey !== -1) {
-      this.workerNodes.splice(workerNodeKey, 1)
-      this.workerChoiceStrategiesContext?.remove(workerNodeKey)
-    }
-    this.checkAndEmitEmptyEvent()
-  }
-
-  protected flagWorkerNodeAsNotReady (workerNodeKey: number): void {
-    const workerInfo = this.getWorkerInfo(workerNodeKey)
-    if (workerInfo != null) {
-      workerInfo.ready = false
+  private get utilization (): number {
+    if (this.startTimestamp == null) {
+      return 0
     }
-  }
-
-  private hasBackPressure (): boolean {
-    return (
-      this.opts.enableTasksQueue === true &&
-      this.workerNodes.findIndex(
-        workerNode => !workerNode.hasBackPressure()
-      ) === -1
+    const poolTimeCapacity =
+      (performance.now() - this.startTimestamp) *
+      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
+    const totalTasksRunTime = this.workerNodes.reduce(
+      (accumulator, workerNode) =>
+        accumulator + (workerNode.usage.runTime.aggregate ?? 0),
+      0
+    )
+    const totalTasksWaitTime = this.workerNodes.reduce(
+      (accumulator, workerNode) =>
+        accumulator + (workerNode.usage.waitTime.aggregate ?? 0),
+      0
     )
+    return (totalTasksRunTime + totalTasksWaitTime) / poolTimeCapacity
   }
 
   /**
-   * Executes the given task on the worker given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @param task - The task to execute.
+   * The worker type.
    */
-  private executeTask (workerNodeKey: number, task: Task<Data>): void {
-    this.beforeTaskExecutionHook(workerNodeKey, task)
-    this.sendToWorker(workerNodeKey, task, task.transferList)
-    this.checkAndEmitTaskExecutionEvents()
-  }
-
-  private enqueueTask (workerNodeKey: number, task: Task<Data>): number {
-    const tasksQueueSize = this.workerNodes[workerNodeKey].enqueueTask(task)
-    this.checkAndEmitTaskQueuingEvents()
-    return tasksQueueSize
-  }
-
-  private dequeueTask (workerNodeKey: number): Task<Data> | undefined {
-    return this.workerNodes[workerNodeKey].dequeueTask()
-  }
-
-  private tasksQueueSize (workerNodeKey: number): number {
-    return this.workerNodes[workerNodeKey].tasksQueueSize()
-  }
-
-  protected flushTasksQueue (workerNodeKey: number): number {
-    let flushedTasks = 0
-    while (this.tasksQueueSize(workerNodeKey) > 0) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.executeTask(workerNodeKey, this.dequeueTask(workerNodeKey)!)
-      ++flushedTasks
-    }
-    this.workerNodes[workerNodeKey].clearTasksQueue()
-    return flushedTasks
-  }
-
-  private flushTasksQueues (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.flushTasksQueue(workerNodeKey)
-    }
-  }
+  protected abstract get worker (): WorkerType
 }
index 47799f5ef6352cd6f3f2fbe68b4545fb67ab420b..f13ebf645dde633a4d4db9dee5be0c0a2c949b6d 100644 (file)
@@ -36,11 +36,6 @@ export class DynamicClusterPool<
     )
   }
 
-  /** @inheritDoc */
-  protected shallCreateDynamicWorker (): boolean {
-    return (!this.full && this.internalBusy()) || this.empty
-  }
-
   /** @inheritDoc */
   protected checkAndEmitDynamicWorkerCreationEvents (): void {
     if (this.full) {
@@ -49,12 +44,17 @@ export class DynamicClusterPool<
   }
 
   /** @inheritDoc */
-  protected get type (): PoolType {
-    return PoolTypes.dynamic
+  protected shallCreateDynamicWorker (): boolean {
+    return (!this.full && this.internalBusy()) || this.empty
   }
 
   /** @inheritDoc */
   protected get busy (): boolean {
     return this.full && this.internalBusy()
   }
+
+  /** @inheritDoc */
+  protected get type (): PoolType {
+    return PoolTypes.dynamic
+  }
 }
index bf61cd477b153af111d835a0371d193d92ceb786..2a12e08054debaf784d769d1e58f3c071bd57e1a 100644 (file)
@@ -1,6 +1,7 @@
 import cluster, { type Worker } from 'node:cluster'
 
 import type { MessageValue } from '../../utility-types.js'
+
 import { AbstractPool } from '../abstract-pool.js'
 import { type PoolOptions, type PoolType, PoolTypes } from '../pool.js'
 import { type WorkerType, WorkerTypes } from '../worker.js'
@@ -38,8 +39,16 @@ export class FixedClusterPool<
   }
 
   /** @inheritDoc */
-  protected setupHook (): void {
-    cluster.setupPrimary({ ...this.opts.settings, exec: this.filePath })
+  protected checkAndEmitDynamicWorkerCreationEvents (): void {
+    /* noop */
+  }
+
+  /** @inheritDoc */
+  protected deregisterWorkerMessageListener<Message extends Data | Response>(
+    workerNodeKey: number,
+    listener: (message: MessageValue<Message>) => void
+  ): void {
+    this.workerNodes[workerNodeKey].worker.off('message', listener)
   }
 
   /** @inheritDoc */
@@ -48,21 +57,11 @@ export class FixedClusterPool<
   }
 
   /** @inheritDoc */
-  protected sendToWorker (
+  protected registerOnceWorkerMessageListener<Message extends Data | Response>(
     workerNodeKey: number,
-    message: MessageValue<Data>
+    listener: (message: MessageValue<Message>) => void
   ): void {
-    this.workerNodes[workerNodeKey]?.worker.send({
-      ...message,
-      workerId: this.getWorkerInfo(workerNodeKey)?.id,
-    } satisfies MessageValue<Data>)
-  }
-
-  /** @inheritDoc */
-  protected sendStartupMessageToWorker (workerNodeKey: number): void {
-    this.sendToWorker(workerNodeKey, {
-      ready: false,
-    })
+    this.workerNodes[workerNodeKey].worker.once('message', listener)
   }
 
   /** @inheritDoc */
@@ -74,19 +73,26 @@ export class FixedClusterPool<
   }
 
   /** @inheritDoc */
-  protected registerOnceWorkerMessageListener<Message extends Data | Response>(
-    workerNodeKey: number,
-    listener: (message: MessageValue<Message>) => void
-  ): void {
-    this.workerNodes[workerNodeKey].worker.once('message', listener)
+  protected sendStartupMessageToWorker (workerNodeKey: number): void {
+    this.sendToWorker(workerNodeKey, {
+      ready: false,
+    })
   }
 
   /** @inheritDoc */
-  protected deregisterWorkerMessageListener<Message extends Data | Response>(
+  protected sendToWorker (
     workerNodeKey: number,
-    listener: (message: MessageValue<Message>) => void
+    message: MessageValue<Data>
   ): void {
-    this.workerNodes[workerNodeKey].worker.off('message', listener)
+    this.workerNodes[workerNodeKey]?.worker.send({
+      ...message,
+      workerId: this.getWorkerInfo(workerNodeKey)?.id,
+    } satisfies MessageValue<Data>)
+  }
+
+  /** @inheritDoc */
+  protected setupHook (): void {
+    cluster.setupPrimary({ ...this.opts.settings, exec: this.filePath })
   }
 
   /** @inheritDoc */
@@ -95,8 +101,8 @@ export class FixedClusterPool<
   }
 
   /** @inheritDoc */
-  protected checkAndEmitDynamicWorkerCreationEvents (): void {
-    /* noop */
+  protected get busy (): boolean {
+    return this.internalBusy()
   }
 
   /** @inheritDoc */
@@ -108,9 +114,4 @@ export class FixedClusterPool<
   protected get worker (): WorkerType {
     return WorkerTypes.cluster
   }
-
-  /** @inheritDoc */
-  protected get busy (): boolean {
-    return this.internalBusy()
-  }
 }
index b6c343bbde782d8de6a67792c573209741c3b14d..a9493f9ab1f536219b5da0a31809263d0556a3f3 100644 (file)
@@ -25,17 +25,17 @@ import type {
  * Enumeration of pool types.
  */
 export const PoolTypes: Readonly<{
-  fixed: 'fixed'
   dynamic: 'dynamic'
+  fixed: 'fixed'
 }> = Object.freeze({
-  /**
-   * Fixed pool type.
-   */
-  fixed: 'fixed',
   /**
    * Dynamic pool type.
    */
   dynamic: 'dynamic',
+  /**
+   * Fixed pool type.
+   */
+  fixed: 'fixed',
 } as const)
 
 /**
@@ -47,23 +47,23 @@ export type PoolType = keyof typeof PoolTypes
  * Enumeration of pool events.
  */
 export const PoolEvents: Readonly<{
-  ready: 'ready'
+  backPressure: 'backPressure'
   busy: 'busy'
-  full: 'full'
-  empty: 'empty'
   destroy: 'destroy'
+  empty: 'empty'
   error: 'error'
+  full: 'full'
+  ready: 'ready'
   taskError: 'taskError'
-  backPressure: 'backPressure'
 }> = Object.freeze({
-  ready: 'ready',
+  backPressure: 'backPressure',
   busy: 'busy',
-  full: 'full',
-  empty: 'empty',
   destroy: 'destroy',
+  empty: 'empty',
   error: 'error',
+  full: 'full',
+  ready: 'ready',
   taskError: 'taskError',
-  backPressure: 'backPressure',
 } as const)
 
 /**
@@ -75,85 +75,85 @@ export type PoolEvent = keyof typeof PoolEvents
  * Pool information.
  */
 export interface PoolInfo {
-  readonly version: string
-  readonly type: PoolType
-  readonly worker: WorkerType
-  readonly started: boolean
-  readonly ready: boolean
-  readonly defaultStrategy: WorkerChoiceStrategy
-  readonly strategyRetries: number
-  readonly minSize: number
-  readonly maxSize: number
-  /** Pool utilization. */
-  readonly utilization?: number
-  /** Pool total worker nodes. */
-  readonly workerNodes: number
-  /** Pool idle worker nodes. */
-  readonly idleWorkerNodes: number
-  /** Pool busy worker nodes. */
-  readonly busyWorkerNodes: number
-  /** Pool tasks stealing worker nodes. */
-  readonly stealingWorkerNodes?: number
+  readonly backPressure?: boolean
   /** Pool tasks back pressure worker nodes. */
   readonly backPressureWorkerNodes?: number
-  readonly executedTasks: number
-  readonly executingTasks: number
-  readonly queuedTasks?: number
-  readonly maxQueuedTasks?: number
-  readonly backPressure?: boolean
-  readonly stolenTasks?: number
-  readonly failedTasks: number
-  readonly runTime?: {
-    readonly minimum: number
-    readonly maximum: number
-    readonly average?: number
-    readonly median?: number
-  }
-  readonly waitTime?: {
-    readonly minimum: number
-    readonly maximum: number
-    readonly average?: number
-    readonly median?: number
-  }
+  /** Pool busy worker nodes. */
+  readonly busyWorkerNodes: number
+  readonly defaultStrategy: WorkerChoiceStrategy
   readonly elu?: {
-    idle: {
-      readonly minimum: number
-      readonly maximum: number
+    active: {
       readonly average?: number
+      readonly maximum: number
       readonly median?: number
-    }
-    active: {
       readonly minimum: number
-      readonly maximum: number
+    }
+    idle: {
       readonly average?: number
+      readonly maximum: number
       readonly median?: number
+      readonly minimum: number
     }
     utilization: {
       readonly average?: number
       readonly median?: number
     }
   }
+  readonly executedTasks: number
+  readonly executingTasks: number
+  readonly failedTasks: number
+  /** Pool idle worker nodes. */
+  readonly idleWorkerNodes: number
+  readonly maxQueuedTasks?: number
+  readonly maxSize: number
+  readonly minSize: number
+  readonly queuedTasks?: number
+  readonly ready: boolean
+  readonly runTime?: {
+    readonly average?: number
+    readonly maximum: number
+    readonly median?: number
+    readonly minimum: number
+  }
+  readonly started: boolean
+  /** Pool tasks stealing worker nodes. */
+  readonly stealingWorkerNodes?: number
+  readonly stolenTasks?: number
+  readonly strategyRetries: number
+  readonly type: PoolType
+  /** Pool utilization. */
+  readonly utilization?: number
+  readonly version: string
+  readonly waitTime?: {
+    readonly average?: number
+    readonly maximum: number
+    readonly median?: number
+    readonly minimum: number
+  }
+  readonly worker: WorkerType
+  /** Pool total worker nodes. */
+  readonly workerNodes: number
 }
 
 /**
  * Worker node tasks queue options.
  */
 export interface TasksQueueOptions {
-  /**
-   * Maximum tasks queue size per worker node flagging it as back pressured.
-   * @defaultValue (pool maximum size)^2
-   */
-  readonly size?: number
   /**
    * Maximum number of tasks that can be executed concurrently on a worker node.
    * @defaultValue 1
    */
   readonly concurrency?: number
   /**
-   * Whether to enable task stealing on idle.
-   * @defaultValue true
+   * Maximum tasks queue size per worker node flagging it as back pressured.
+   * @defaultValue (pool maximum size)^2
    */
-  readonly taskStealing?: boolean
+  readonly size?: number
+  /**
+   * Queued tasks finished timeout in milliseconds at worker node termination.
+   * @defaultValue 2000
+   */
+  readonly tasksFinishedTimeout?: number
   /**
    * Whether to enable tasks stealing under back pressure.
    * @defaultValue true
@@ -165,10 +165,10 @@ export interface TasksQueueOptions {
    */
   readonly tasksStealingRatio?: number
   /**
-   * Queued tasks finished timeout in milliseconds at worker node termination.
-   * @defaultValue 2000
+   * Whether to enable task stealing on idle.
+   * @defaultValue true
    */
-  readonly tasksFinishedTimeout?: number
+  readonly taskStealing?: boolean
 }
 
 /**
@@ -177,15 +177,20 @@ export interface TasksQueueOptions {
  */
 export interface PoolOptions<Worker extends IWorker> {
   /**
-   * A function that will listen for online event on each worker.
-   * @defaultValue `() => {}`
+   * Pool events integrated with async resource emission.
+   * @defaultValue true
    */
-  onlineHandler?: OnlineHandler<Worker>
+  enableEvents?: boolean
   /**
-   * A function that will listen for message event on each worker.
-   * @defaultValue `() => {}`
+   * Pool worker node tasks queue.
+   * @defaultValue false
    */
-  messageHandler?: MessageHandler<Worker>
+  enableTasksQueue?: boolean
+  /**
+   * Key/value pairs to add to worker process environment.
+   * @see https://nodejs.org/api/cluster.html#cluster_cluster_fork_env
+   */
+  env?: Record<string, unknown>
   /**
    * A function that will listen for error event on each worker.
    * @defaultValue `() => {}`
@@ -197,52 +202,47 @@ export interface PoolOptions<Worker extends IWorker> {
    */
   exitHandler?: ExitHandler<Worker>
   /**
-   * Whether to start the minimum number of workers at pool initialization.
-   * @defaultValue true
-   */
-  startWorkers?: boolean
-  /**
-   * The default worker choice strategy to use in this pool.
-   * @defaultValue WorkerChoiceStrategies.ROUND_ROBIN
+   * A function that will listen for message event on each worker.
+   * @defaultValue `() => {}`
    */
-  workerChoiceStrategy?: WorkerChoiceStrategy
+  messageHandler?: MessageHandler<Worker>
   /**
-   * The worker choice strategy options.
+   * A function that will listen for online event on each worker.
+   * @defaultValue `() => {}`
    */
-  workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions
+  onlineHandler?: OnlineHandler<Worker>
   /**
    * Restart worker on error.
    */
   restartWorkerOnError?: boolean
   /**
-   * Pool events integrated with async resource emission.
-   * @defaultValue true
+   * Cluster settings.
+   * @see https://nodejs.org/api/cluster.html#cluster_cluster_settings
    */
-  enableEvents?: boolean
+  settings?: ClusterSettings
   /**
-   * Pool worker node tasks queue.
-   * @defaultValue false
+   * Whether to start the minimum number of workers at pool initialization.
+   * @defaultValue true
    */
-  enableTasksQueue?: boolean
+  startWorkers?: boolean
   /**
    * Pool worker node tasks queue options.
    */
   tasksQueueOptions?: TasksQueueOptions
   /**
-   * Worker options.
-   * @see https://nodejs.org/api/worker_threads.html#new-workerfilename-options
+   * The default worker choice strategy to use in this pool.
+   * @defaultValue WorkerChoiceStrategies.ROUND_ROBIN
    */
-  workerOptions?: WorkerOptions
+  workerChoiceStrategy?: WorkerChoiceStrategy
   /**
-   * Key/value pairs to add to worker process environment.
-   * @see https://nodejs.org/api/cluster.html#cluster_cluster_fork_env
+   * The worker choice strategy options.
    */
-  env?: Record<string, unknown>
+  workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions
   /**
-   * Cluster settings.
-   * @see https://nodejs.org/api/cluster.html#cluster_cluster_settings
+   * Worker options.
+   * @see https://nodejs.org/api/worker_threads.html#new-workerfilename-options
    */
-  settings?: ClusterSettings
+  workerOptions?: WorkerOptions
 }
 
 /**
@@ -257,14 +257,22 @@ export interface IPool<
   Response = unknown
 > {
   /**
-   * Pool information.
+   * Adds a task function to this pool.
+   * If a task function with the same name already exists, it will be overwritten.
+   * @param name - The name of the task function.
+   * @param fn - The task function.
+   * @returns `true` if the task function was added, `false` otherwise.
+   * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `name` parameter is not a string or an empty string.
+   * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `fn` parameter is not a function or task function object.
    */
-  readonly info: PoolInfo
+  readonly addTaskFunction: (
+    name: string,
+    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
+  ) => Promise<boolean>
   /**
-   * Pool worker nodes.
-   * @internal
+   * Terminates all workers in this pool.
    */
-  readonly workerNodes: IWorkerNode<Worker, Data>[]
+  readonly destroy: () => Promise<void>
   /**
    * Pool event emitter integrated with async resource.
    * The async tracking tooling identifier is `poolifier:<PoolType>-<WorkerType>-pool`.
@@ -281,6 +289,15 @@ export interface IPool<
    * - `'backPressure'`: Emitted when all worker nodes have back pressure (i.e. their tasks queue is full: queue size \>= maximum queue size).
    */
   readonly emitter?: EventEmitterAsyncResource
+  /**
+   * Enables/disables the worker node tasks queue in this pool.
+   * @param enable - Whether to enable or disable the worker node tasks queue.
+   * @param tasksQueueOptions - The worker node tasks queue options.
+   */
+  readonly enableTasksQueue: (
+    enable: boolean,
+    tasksQueueOptions?: TasksQueueOptions
+  ) => void
   /**
    * Executes the specified function in the worker constructor with the task data input parameter.
    * @param data - The optional task input data for the specified task function. This can only be structured-cloneable data.
@@ -293,6 +310,21 @@ export interface IPool<
     name?: string,
     transferList?: readonly TransferListItem[]
   ) => Promise<Response>
+  /**
+   * Whether the specified task function exists in this pool.
+   * @param name - The name of the task function.
+   * @returns `true` if the task function exists, `false` otherwise.
+   */
+  readonly hasTaskFunction: (name: string) => boolean
+  /**
+   * Pool information.
+   */
+  readonly info: PoolInfo
+  /**
+   * Lists the properties of task functions available in this pool.
+   * @returns The properties of task functions available in this pool.
+   */
+  readonly listTaskFunctionsProperties: () => TaskFunctionProperties[]
   /**
    * Executes the specified function in the worker constructor with the tasks data iterable input parameter.
    * @param data - The tasks iterable input data for the specified task function. This can only be an iterable of structured-cloneable data.
@@ -305,50 +337,23 @@ export interface IPool<
     name?: string,
     transferList?: readonly TransferListItem[]
   ) => Promise<Response[]>
-  /**
-   * Starts the minimum number of workers in this pool.
-   */
-  readonly start: () => void
-  /**
-   * Terminates all workers in this pool.
-   */
-  readonly destroy: () => Promise<void>
-  /**
-   * Whether the specified task function exists in this pool.
-   * @param name - The name of the task function.
-   * @returns `true` if the task function exists, `false` otherwise.
-   */
-  readonly hasTaskFunction: (name: string) => boolean
-  /**
-   * Adds a task function to this pool.
-   * If a task function with the same name already exists, it will be overwritten.
-   * @param name - The name of the task function.
-   * @param fn - The task function.
-   * @returns `true` if the task function was added, `false` otherwise.
-   * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `name` parameter is not a string or an empty string.
-   * @throws {@link https://nodejs.org/api/errors.html#class-typeerror} If the `fn` parameter is not a function or task function object.
-   */
-  readonly addTaskFunction: (
-    name: string,
-    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
-  ) => Promise<boolean>
   /**
    * Removes a task function from this pool.
    * @param name - The name of the task function.
    * @returns `true` if the task function was removed, `false` otherwise.
    */
   readonly removeTaskFunction: (name: string) => Promise<boolean>
-  /**
-   * Lists the properties of task functions available in this pool.
-   * @returns The properties of task functions available in this pool.
-   */
-  readonly listTaskFunctionsProperties: () => TaskFunctionProperties[]
   /**
    * Sets the default task function in this pool.
    * @param name - The name of the task function.
    * @returns `true` if the default task function was set, `false` otherwise.
    */
   readonly setDefaultTaskFunction: (name: string) => Promise<boolean>
+  /**
+   * Sets the worker node tasks queue options in this pool.
+   * @param tasksQueueOptions - The worker node tasks queue options.
+   */
+  readonly setTasksQueueOptions: (tasksQueueOptions: TasksQueueOptions) => void
   /**
    * Sets the default worker choice strategy in this pool.
    * @param workerChoiceStrategy - The default worker choice strategy.
@@ -367,17 +372,12 @@ export interface IPool<
     workerChoiceStrategyOptions: WorkerChoiceStrategyOptions
   ) => boolean
   /**
-   * Enables/disables the worker node tasks queue in this pool.
-   * @param enable - Whether to enable or disable the worker node tasks queue.
-   * @param tasksQueueOptions - The worker node tasks queue options.
+   * Starts the minimum number of workers in this pool.
    */
-  readonly enableTasksQueue: (
-    enable: boolean,
-    tasksQueueOptions?: TasksQueueOptions
-  ) => void
+  readonly start: () => void
   /**
-   * Sets the worker node tasks queue options in this pool.
-   * @param tasksQueueOptions - The worker node tasks queue options.
+   * Pool worker nodes.
+   * @internal
    */
-  readonly setTasksQueueOptions: (tasksQueueOptions: TasksQueueOptions) => void
+  readonly workerNodes: IWorkerNode<Worker, Data>[]
 }
index f4273dcd9d17bc3e20f56795f0891a0dbf4b2d56..ef14015d2fc18ad4fdd7ae5bb954f1ee2a91679f 100644 (file)
@@ -1,5 +1,4 @@
 import type { IPool } from '../pool.js'
-import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import type { IWorker } from '../worker.js'
 import type {
   IWorkerChoiceStrategy,
@@ -7,6 +6,8 @@ import type {
   TaskStatisticsRequirements,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
+
+import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import {
   buildWorkerChoiceStrategyOptions,
   toggleMedianMeasurementStatisticsRequirements,
@@ -35,15 +36,15 @@ export abstract class AbstractWorkerChoiceStrategy<
 
   /** @inheritDoc */
   public readonly strategyPolicy: StrategyPolicy = {
-    dynamicWorkerUsage: false,
     dynamicWorkerReady: true,
+    dynamicWorkerUsage: false,
   }
 
   /** @inheritDoc */
   public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
+    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
     runTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
     waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
-    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
   }
 
   /**
@@ -59,61 +60,6 @@ export abstract class AbstractWorkerChoiceStrategy<
     this.setOptions(this.opts)
   }
 
-  protected setTaskStatisticsRequirements (
-    opts: WorkerChoiceStrategyOptions | undefined
-  ): void {
-    toggleMedianMeasurementStatisticsRequirements(
-      this.taskStatisticsRequirements.runTime,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      opts!.runTime!.median
-    )
-    toggleMedianMeasurementStatisticsRequirements(
-      this.taskStatisticsRequirements.waitTime,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      opts!.waitTime!.median
-    )
-    toggleMedianMeasurementStatisticsRequirements(
-      this.taskStatisticsRequirements.elu,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      opts!.elu!.median
-    )
-  }
-
-  protected resetWorkerNodeKeyProperties (): void {
-    this.nextWorkerNodeKey = 0
-    this.previousWorkerNodeKey = 0
-  }
-
-  /** @inheritDoc */
-  public abstract reset (): boolean
-
-  /** @inheritDoc */
-  public abstract update (workerNodeKey: number): boolean
-
-  /** @inheritDoc */
-  public abstract choose (): number | undefined
-
-  /** @inheritDoc */
-  public abstract remove (workerNodeKey: number): boolean
-
-  /** @inheritDoc */
-  public setOptions (opts: WorkerChoiceStrategyOptions | undefined): void {
-    this.opts = buildWorkerChoiceStrategyOptions<Worker, Data, Response>(
-      this.pool,
-      opts
-    )
-    this.setTaskStatisticsRequirements(this.opts)
-  }
-
-  /**
-   * Whether the worker node is ready or not.
-   * @param workerNodeKey - The worker node key.
-   * @returns Whether the worker node is ready or not.
-   */
-  protected isWorkerNodeReady (workerNodeKey: number): boolean {
-    return this.pool.workerNodes[workerNodeKey]?.info?.ready ?? false
-  }
-
   /**
    * Check the next worker node key.
    */
@@ -127,6 +73,19 @@ export abstract class AbstractWorkerChoiceStrategy<
     }
   }
 
+  /**
+   * Gets the worker node task ELU.
+   * If the task statistics require the average ELU, the average ELU is returned.
+   * If the task statistics require the median ELU, the median ELU is returned.
+   * @param workerNodeKey - The worker node key.
+   * @returns The worker node task ELU.
+   */
+  protected getWorkerNodeTaskElu (workerNodeKey: number): number {
+    return this.taskStatisticsRequirements.elu.median
+      ? this.pool.workerNodes[workerNodeKey].usage.elu.active.median ?? 0
+      : this.pool.workerNodes[workerNodeKey].usage.elu.active.average ?? 0
+  }
+
   /**
    * Gets the worker node task runtime.
    * If the task statistics require the average runtime, the average runtime is returned.
@@ -154,16 +113,17 @@ export abstract class AbstractWorkerChoiceStrategy<
   }
 
   /**
-   * Gets the worker node task ELU.
-   * If the task statistics require the average ELU, the average ELU is returned.
-   * If the task statistics require the median ELU, the median ELU is returned.
+   * Whether the worker node is ready or not.
    * @param workerNodeKey - The worker node key.
-   * @returns The worker node task ELU.
+   * @returns Whether the worker node is ready or not.
    */
-  protected getWorkerNodeTaskElu (workerNodeKey: number): number {
-    return this.taskStatisticsRequirements.elu.median
-      ? this.pool.workerNodes[workerNodeKey].usage.elu.active.median ?? 0
-      : this.pool.workerNodes[workerNodeKey].usage.elu.active.average ?? 0
+  protected isWorkerNodeReady (workerNodeKey: number): boolean {
+    return this.pool.workerNodes[workerNodeKey]?.info?.ready ?? false
+  }
+
+  protected resetWorkerNodeKeyProperties (): void {
+    this.nextWorkerNodeKey = 0
+    this.previousWorkerNodeKey = 0
   }
 
   /**
@@ -176,4 +136,45 @@ export abstract class AbstractWorkerChoiceStrategy<
         ? workerNodeKey
         : this.previousWorkerNodeKey
   }
+
+  protected setTaskStatisticsRequirements (
+    opts: undefined | WorkerChoiceStrategyOptions
+  ): void {
+    toggleMedianMeasurementStatisticsRequirements(
+      this.taskStatisticsRequirements.runTime,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      opts!.runTime!.median
+    )
+    toggleMedianMeasurementStatisticsRequirements(
+      this.taskStatisticsRequirements.waitTime,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      opts!.waitTime!.median
+    )
+    toggleMedianMeasurementStatisticsRequirements(
+      this.taskStatisticsRequirements.elu,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      opts!.elu!.median
+    )
+  }
+
+  /** @inheritDoc */
+  public abstract choose (): number | undefined
+
+  /** @inheritDoc */
+  public abstract remove (workerNodeKey: number): boolean
+
+  /** @inheritDoc */
+  public abstract reset (): boolean
+
+  /** @inheritDoc */
+  public setOptions (opts: undefined | WorkerChoiceStrategyOptions): void {
+    this.opts = buildWorkerChoiceStrategyOptions<Worker, Data, Response>(
+      this.pool,
+      opts
+    )
+    this.setTaskStatisticsRequirements(this.opts)
+  }
+
+  /** @inheritDoc */
+  public abstract update (workerNodeKey: number): boolean
 }
index 88e12b94fa8ef50b50ede22026e95f0242000d45..92c8546cd5d1d0ad77c3c879160649864db168ed 100644 (file)
@@ -1,5 +1,6 @@
 import type { IPool } from '../pool.js'
 import type { IWorker } from '../worker.js'
+
 import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import {
   type IWorkerChoiceStrategy,
@@ -24,17 +25,17 @@ export class FairShareWorkerChoiceStrategy<
   implements IWorkerChoiceStrategy {
   /** @inheritDoc */
   public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
-    runTime: {
+    elu: {
       aggregate: true,
       average: true,
       median: false,
     },
-    waitTime: {
+    runTime: {
       aggregate: true,
       average: true,
       median: false,
     },
-    elu: {
+    waitTime: {
       aggregate: true,
       average: true,
       median: false,
@@ -50,33 +51,18 @@ export class FairShareWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
-  /** @inheritDoc */
-  public reset (): boolean {
-    for (const workerNode of this.pool.workerNodes) {
-      delete workerNode.strategyData?.virtualTaskEndTimestamp
-    }
-    return true
-  }
-
-  /** @inheritDoc */
-  public update (workerNodeKey: number): boolean {
-    this.pool.workerNodes[workerNodeKey].strategyData = {
-      virtualTaskEndTimestamp:
-        this.computeWorkerNodeVirtualTaskEndTimestamp(workerNodeKey),
-    }
-    return true
-  }
-
-  /** @inheritDoc */
-  public choose (): number | undefined {
-    this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey)
-    this.nextWorkerNodeKey = this.fairShareNextWorkerNodeKey()
-    return this.nextWorkerNodeKey
-  }
-
-  /** @inheritDoc */
-  public remove (): boolean {
-    return true
+  /**
+   * Computes the worker node key virtual task end timestamp.
+   * @param workerNodeKey - The worker node key.
+   * @returns The worker node key virtual task end timestamp.
+   */
+  private computeWorkerNodeVirtualTaskEndTimestamp (
+    workerNodeKey: number
+  ): number {
+    return this.getWorkerNodeVirtualTaskEndTimestamp(
+      workerNodeKey,
+      this.getWorkerNodeVirtualTaskStartTimestamp(workerNodeKey)
+    )
   }
 
   private fairShareNextWorkerNodeKey (): number | undefined {
@@ -100,20 +86,6 @@ export class FairShareWorkerChoiceStrategy<
     )
   }
 
-  /**
-   * Computes the worker node key virtual task end timestamp.
-   * @param workerNodeKey - The worker node key.
-   * @returns The worker node key virtual task end timestamp.
-   */
-  private computeWorkerNodeVirtualTaskEndTimestamp (
-    workerNodeKey: number
-  ): number {
-    return this.getWorkerNodeVirtualTaskEndTimestamp(
-      workerNodeKey,
-      this.getWorkerNodeVirtualTaskStartTimestamp(workerNodeKey)
-    )
-  }
-
   private getWorkerNodeVirtualTaskEndTimestamp (
     workerNodeKey: number,
     workerNodeVirtualTaskStartTimestamp: number
@@ -139,4 +111,33 @@ export class FairShareWorkerChoiceStrategy<
       virtualTaskEndTimestamp!
       : now
   }
+
+  /** @inheritDoc */
+  public choose (): number | undefined {
+    this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey)
+    this.nextWorkerNodeKey = this.fairShareNextWorkerNodeKey()
+    return this.nextWorkerNodeKey
+  }
+
+  /** @inheritDoc */
+  public remove (): boolean {
+    return true
+  }
+
+  /** @inheritDoc */
+  public reset (): boolean {
+    for (const workerNode of this.pool.workerNodes) {
+      delete workerNode.strategyData?.virtualTaskEndTimestamp
+    }
+    return true
+  }
+
+  /** @inheritDoc */
+  public update (workerNodeKey: number): boolean {
+    this.pool.workerNodes[workerNodeKey].strategyData = {
+      virtualTaskEndTimestamp:
+        this.computeWorkerNodeVirtualTaskEndTimestamp(workerNodeKey),
+    }
+    return true
+  }
 }
index 61dd2cff38f2c0337c6b3b20706fe209ffc27021..36871f3cf46e2a967962ae74c426d1962da0aa7c 100644 (file)
@@ -1,13 +1,14 @@
 import type { IPool } from '../pool.js'
-import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import type { IWorker } from '../worker.js'
-import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import type {
   IWorkerChoiceStrategy,
   TaskStatisticsRequirements,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
 
+import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
+import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
+
 /**
  * Selects the next worker with an interleaved weighted round robin scheduling algorithm.
  * @typeParam Worker - Type of worker which manages the strategy.
@@ -21,25 +22,11 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
   >
   extends AbstractWorkerChoiceStrategy<Worker, Data, Response>
   implements IWorkerChoiceStrategy {
-  /** @inheritDoc */
-  public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
-    runTime: {
-      aggregate: true,
-      average: true,
-      median: false,
-    },
-    waitTime: {
-      aggregate: true,
-      average: true,
-      median: false,
-    },
-    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
-  }
-
   /**
    * Round id.
    */
   private roundId = 0
+
   /**
    * Round weights.
    */
@@ -52,6 +39,20 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
    * Worker node virtual execution time.
    */
   private workerNodeVirtualTaskExecutionTime = 0
+  /** @inheritDoc */
+  public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
+    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
+    runTime: {
+      aggregate: true,
+      average: true,
+      median: false,
+    },
+    waitTime: {
+      aggregate: true,
+      average: true,
+      median: false,
+    },
+  }
 
   /** @inheritDoc */
   public constructor (
@@ -63,18 +64,32 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
     this.roundWeights = this.getRoundWeights()
   }
 
-  /** @inheritDoc */
-  public reset (): boolean {
-    this.resetWorkerNodeKeyProperties()
-    this.roundId = 0
-    this.workerNodeId = 0
-    this.workerNodeVirtualTaskExecutionTime = 0
-    return true
+  private getRoundWeights (): number[] {
+    return [
+      ...new Set(
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        Object.values(this.opts!.weights!)
+          .slice()
+          .sort((a, b) => a - b)
+      ),
+    ]
   }
 
-  /** @inheritDoc */
-  public update (): boolean {
-    return true
+  private interleavedWeightedRoundRobinNextWorkerNodeId (): void {
+    if (this.pool.workerNodes.length === 0) {
+      this.workerNodeId = 0
+    } else if (
+      this.roundId === this.roundWeights.length - 1 &&
+      this.workerNodeId === this.pool.workerNodes.length - 1
+    ) {
+      this.roundId = 0
+      this.workerNodeId = 0
+    } else if (this.workerNodeId === this.pool.workerNodes.length - 1) {
+      this.roundId = this.roundId + 1
+      this.workerNodeId = 0
+    } else {
+      this.workerNodeId = this.workerNodeId + 1
+    }
   }
 
   /** @inheritDoc */
@@ -116,23 +131,6 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
     this.interleavedWeightedRoundRobinNextWorkerNodeId()
   }
 
-  private interleavedWeightedRoundRobinNextWorkerNodeId (): void {
-    if (this.pool.workerNodes.length === 0) {
-      this.workerNodeId = 0
-    } else if (
-      this.roundId === this.roundWeights.length - 1 &&
-      this.workerNodeId === this.pool.workerNodes.length - 1
-    ) {
-      this.roundId = 0
-      this.workerNodeId = 0
-    } else if (this.workerNodeId === this.pool.workerNodes.length - 1) {
-      this.roundId = this.roundId + 1
-      this.workerNodeId = 0
-    } else {
-      this.workerNodeId = this.workerNodeId + 1
-    }
-  }
-
   /** @inheritDoc */
   public remove (workerNodeKey: number): boolean {
     if (this.pool.workerNodes.length === 0) {
@@ -157,19 +155,22 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
   }
 
   /** @inheritDoc */
-  public setOptions (opts: WorkerChoiceStrategyOptions | undefined): void {
+  public reset (): boolean {
+    this.resetWorkerNodeKeyProperties()
+    this.roundId = 0
+    this.workerNodeId = 0
+    this.workerNodeVirtualTaskExecutionTime = 0
+    return true
+  }
+
+  /** @inheritDoc */
+  public setOptions (opts: undefined | WorkerChoiceStrategyOptions): void {
     super.setOptions(opts)
     this.roundWeights = this.getRoundWeights()
   }
 
-  private getRoundWeights (): number[] {
-    return [
-      ...new Set(
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        Object.values(this.opts!.weights!)
-          .slice()
-          .sort((a, b) => a - b)
-      ),
-    ]
+  /** @inheritDoc */
+  public update (): boolean {
+    return true
   }
 }
index 33199368c67b176a23920f407973aaf290e3a9be..fba2350476b9de5f19339111c60e08f06cd58e33 100644 (file)
@@ -1,13 +1,14 @@
 import type { IPool } from '../pool.js'
-import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import type { IWorker } from '../worker.js'
-import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import type {
   IWorkerChoiceStrategy,
   TaskStatisticsRequirements,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
 
+import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
+import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
+
 /**
  * Selects the least busy worker.
  * @typeParam Worker - Type of worker which manages the strategy.
@@ -23,6 +24,7 @@ export class LeastBusyWorkerChoiceStrategy<
   implements IWorkerChoiceStrategy {
   /** @inheritDoc */
   public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
+    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
     runTime: {
       aggregate: true,
       average: false,
@@ -33,7 +35,6 @@ export class LeastBusyWorkerChoiceStrategy<
       average: false,
       median: false,
     },
-    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
   }
 
   /** @inheritDoc */
@@ -45,14 +46,19 @@ export class LeastBusyWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
-  /** @inheritDoc */
-  public reset (): boolean {
-    return true
-  }
-
-  /** @inheritDoc */
-  public update (): boolean {
-    return true
+  private leastBusyNextWorkerNodeKey (): number | undefined {
+    return this.pool.workerNodes.reduce(
+      (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
+        return this.isWorkerNodeReady(workerNodeKey) &&
+          (workerNode.usage.waitTime.aggregate ?? 0) +
+            (workerNode.usage.runTime.aggregate ?? 0) <
+            (workerNodes[minWorkerNodeKey].usage.waitTime.aggregate ?? 0) +
+              (workerNodes[minWorkerNodeKey].usage.runTime.aggregate ?? 0)
+          ? workerNodeKey
+          : minWorkerNodeKey
+      },
+      0
+    )
   }
 
   /** @inheritDoc */
@@ -67,18 +73,13 @@ export class LeastBusyWorkerChoiceStrategy<
     return true
   }
 
-  private leastBusyNextWorkerNodeKey (): number | undefined {
-    return this.pool.workerNodes.reduce(
-      (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
-        return this.isWorkerNodeReady(workerNodeKey) &&
-          (workerNode.usage.waitTime.aggregate ?? 0) +
-            (workerNode.usage.runTime.aggregate ?? 0) <
-            (workerNodes[minWorkerNodeKey].usage.waitTime.aggregate ?? 0) +
-              (workerNodes[minWorkerNodeKey].usage.runTime.aggregate ?? 0)
-          ? workerNodeKey
-          : minWorkerNodeKey
-      },
-      0
-    )
+  /** @inheritDoc */
+  public reset (): boolean {
+    return true
+  }
+
+  /** @inheritDoc */
+  public update (): boolean {
+    return true
   }
 }
index ead02e850c8a82b4abf38c7a79b0f2155d19fb0a..c7b86b8fce692722c6cd7fb0a0f192f0478b9ff0 100644 (file)
@@ -1,13 +1,14 @@
 import type { IPool } from '../pool.js'
-import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import type { IWorker } from '../worker.js'
-import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import type {
   IWorkerChoiceStrategy,
   TaskStatisticsRequirements,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
 
+import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
+import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
+
 /**
  * Selects the worker with the least ELU.
  * @typeParam Worker - Type of worker which manages the strategy.
@@ -23,13 +24,13 @@ export class LeastEluWorkerChoiceStrategy<
   implements IWorkerChoiceStrategy {
   /** @inheritDoc */
   public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
-    runTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
-    waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
     elu: {
       aggregate: true,
       average: false,
       median: false,
     },
+    runTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
+    waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
   }
 
   /** @inheritDoc */
@@ -41,14 +42,17 @@ export class LeastEluWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
-  /** @inheritDoc */
-  public reset (): boolean {
-    return true
-  }
-
-  /** @inheritDoc */
-  public update (): boolean {
-    return true
+  private leastEluNextWorkerNodeKey (): number | undefined {
+    return this.pool.workerNodes.reduce(
+      (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
+        return this.isWorkerNodeReady(workerNodeKey) &&
+          (workerNode.usage.elu.active.aggregate ?? 0) <
+            (workerNodes[minWorkerNodeKey].usage.elu.active.aggregate ?? 0)
+          ? workerNodeKey
+          : minWorkerNodeKey
+      },
+      0
+    )
   }
 
   /** @inheritDoc */
@@ -63,16 +67,13 @@ export class LeastEluWorkerChoiceStrategy<
     return true
   }
 
-  private leastEluNextWorkerNodeKey (): number | undefined {
-    return this.pool.workerNodes.reduce(
-      (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
-        return this.isWorkerNodeReady(workerNodeKey) &&
-          (workerNode.usage.elu.active.aggregate ?? 0) <
-            (workerNodes[minWorkerNodeKey].usage.elu.active.aggregate ?? 0)
-          ? workerNodeKey
-          : minWorkerNodeKey
-      },
-      0
-    )
+  /** @inheritDoc */
+  public reset (): boolean {
+    return true
+  }
+
+  /** @inheritDoc */
+  public update (): boolean {
+    return true
   }
 }
index 1ee6b7b8aeaf646488c20f7222e0a8fc45a27f7e..067e1238fbbba9ff9541345ff57ff6c09a7cdeeb 100644 (file)
@@ -1,11 +1,12 @@
 import type { IPool } from '../pool.js'
 import type { IWorker } from '../worker.js'
-import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import type {
   IWorkerChoiceStrategy,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
 
+import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
+
 /**
  * Selects the least used worker.
  * @typeParam Worker - Type of worker which manages the strategy.
@@ -27,14 +28,18 @@ export class LeastUsedWorkerChoiceStrategy<
     super(pool, opts)
   }
 
-  /** @inheritDoc */
-  public reset (): boolean {
-    return true
-  }
-
-  /** @inheritDoc */
-  public update (): boolean {
-    return true
+  private leastUsedNextWorkerNodeKey (): number | undefined {
+    return this.pool.workerNodes.reduce(
+      (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
+        return this.isWorkerNodeReady(workerNodeKey) &&
+          workerNode.usage.tasks.executing + workerNode.usage.tasks.queued <
+            workerNodes[minWorkerNodeKey].usage.tasks.executing +
+              workerNodes[minWorkerNodeKey].usage.tasks.queued
+          ? workerNodeKey
+          : minWorkerNodeKey
+      },
+      0
+    )
   }
 
   /** @inheritDoc */
@@ -49,17 +54,13 @@ export class LeastUsedWorkerChoiceStrategy<
     return true
   }
 
-  private leastUsedNextWorkerNodeKey (): number | undefined {
-    return this.pool.workerNodes.reduce(
-      (minWorkerNodeKey, workerNode, workerNodeKey, workerNodes) => {
-        return this.isWorkerNodeReady(workerNodeKey) &&
-          workerNode.usage.tasks.executing + workerNode.usage.tasks.queued <
-            workerNodes[minWorkerNodeKey].usage.tasks.executing +
-              workerNodes[minWorkerNodeKey].usage.tasks.queued
-          ? workerNodeKey
-          : minWorkerNodeKey
-      },
-      0
-    )
+  /** @inheritDoc */
+  public reset (): boolean {
+    return true
+  }
+
+  /** @inheritDoc */
+  public update (): boolean {
+    return true
   }
 }
index 1f5c499e0103dcddf612fe8e540d92c570d91656..39cb3a0803c00183aebd8e67b78b6084655a3ac8 100644 (file)
@@ -1,11 +1,12 @@
 import type { IPool } from '../pool.js'
 import type { IWorker } from '../worker.js'
-import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import type {
   IWorkerChoiceStrategy,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
 
+import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
+
 /**
  * Selects the next worker in a round robin fashion.
  * @typeParam Worker - Type of worker which manages the strategy.
@@ -27,15 +28,12 @@ export class RoundRobinWorkerChoiceStrategy<
     super(pool, opts)
   }
 
-  /** @inheritDoc */
-  public reset (): boolean {
-    this.resetWorkerNodeKeyProperties()
-    return true
-  }
-
-  /** @inheritDoc */
-  public update (): boolean {
-    return true
+  private roundRobinNextWorkerNodeKey (): number | undefined {
+    this.nextWorkerNodeKey =
+      this.nextWorkerNodeKey === this.pool.workerNodes.length - 1
+        ? 0
+        : (this.nextWorkerNodeKey ?? this.previousWorkerNodeKey) + 1
+    return this.nextWorkerNodeKey
   }
 
   /** @inheritDoc */
@@ -68,11 +66,14 @@ export class RoundRobinWorkerChoiceStrategy<
     return true
   }
 
-  private roundRobinNextWorkerNodeKey (): number | undefined {
-    this.nextWorkerNodeKey =
-      this.nextWorkerNodeKey === this.pool.workerNodes.length - 1
-        ? 0
-        : (this.nextWorkerNodeKey ?? this.previousWorkerNodeKey) + 1
-    return this.nextWorkerNodeKey
+  /** @inheritDoc */
+  public reset (): boolean {
+    this.resetWorkerNodeKeyProperties()
+    return true
+  }
+
+  /** @inheritDoc */
+  public update (): boolean {
+    return true
   }
 }
index 745b140a90873beb9a8bf31d49c48033ea257533..236a3adfff3594bc6b30aa2692d467845082d0a4 100644 (file)
@@ -2,22 +2,23 @@
  * Enumeration of worker choice strategies.
  */
 export const WorkerChoiceStrategies: Readonly<{
-  ROUND_ROBIN: 'ROUND_ROBIN'
-  LEAST_USED: 'LEAST_USED'
+  FAIR_SHARE: 'FAIR_SHARE'
+  INTERLEAVED_WEIGHTED_ROUND_ROBIN: 'INTERLEAVED_WEIGHTED_ROUND_ROBIN'
   LEAST_BUSY: 'LEAST_BUSY'
   LEAST_ELU: 'LEAST_ELU'
-  FAIR_SHARE: 'FAIR_SHARE'
+  LEAST_USED: 'LEAST_USED'
+  ROUND_ROBIN: 'ROUND_ROBIN'
   WEIGHTED_ROUND_ROBIN: 'WEIGHTED_ROUND_ROBIN'
-  INTERLEAVED_WEIGHTED_ROUND_ROBIN: 'INTERLEAVED_WEIGHTED_ROUND_ROBIN'
 }> = Object.freeze({
   /**
-   * Round robin worker selection strategy.
+   * Fair share worker selection strategy.
    */
-  ROUND_ROBIN: 'ROUND_ROBIN',
+  FAIR_SHARE: 'FAIR_SHARE',
   /**
-   * Least used worker selection strategy.
+   * Interleaved weighted round robin worker selection strategy.
+   * @experimental
    */
-  LEAST_USED: 'LEAST_USED',
+  INTERLEAVED_WEIGHTED_ROUND_ROBIN: 'INTERLEAVED_WEIGHTED_ROUND_ROBIN',
   /**
    * Least busy worker selection strategy.
    */
@@ -27,18 +28,17 @@ export const WorkerChoiceStrategies: Readonly<{
    */
   LEAST_ELU: 'LEAST_ELU',
   /**
-   * Fair share worker selection strategy.
+   * Least used worker selection strategy.
    */
-  FAIR_SHARE: 'FAIR_SHARE',
+  LEAST_USED: 'LEAST_USED',
   /**
-   * Weighted round robin worker selection strategy.
+   * Round robin worker selection strategy.
    */
-  WEIGHTED_ROUND_ROBIN: 'WEIGHTED_ROUND_ROBIN',
+  ROUND_ROBIN: 'ROUND_ROBIN',
   /**
-   * Interleaved weighted round robin worker selection strategy.
-   * @experimental
+   * Weighted round robin worker selection strategy.
    */
-  INTERLEAVED_WEIGHTED_ROUND_ROBIN: 'INTERLEAVED_WEIGHTED_ROUND_ROBIN',
+  WEIGHTED_ROUND_ROBIN: 'WEIGHTED_ROUND_ROBIN',
 } as const)
 
 /**
@@ -50,13 +50,13 @@ export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies
  * Enumeration of measurements.
  */
 export const Measurements: Readonly<{
+  elu: 'elu'
   runTime: 'runTime'
   waitTime: 'waitTime'
-  elu: 'elu'
 }> = Object.freeze({
+  elu: 'elu',
   runTime: 'runTime',
   waitTime: 'waitTime',
-  elu: 'elu',
 } as const)
 
 /**
@@ -78,6 +78,11 @@ export interface MeasurementOptions {
  * Worker choice strategy options.
  */
 export interface WorkerChoiceStrategyOptions {
+  /**
+   * Event loop utilization options.
+   * @defaultValue \{ median: false \}
+   */
+  readonly elu?: MeasurementOptions
   /**
    * Measurement to use in worker choice strategy supporting it.
    */
@@ -92,11 +97,6 @@ export interface WorkerChoiceStrategyOptions {
    * @defaultValue \{ median: false \}
    */
   readonly waitTime?: MeasurementOptions
-  /**
-   * Event loop utilization options.
-   * @defaultValue \{ median: false \}
-   */
-  readonly elu?: MeasurementOptions
   /**
    * Worker weights to use for weighted round robin worker selection strategies.
    * A weight is tasks maximum execution time in milliseconds for a worker node.
@@ -129,6 +129,10 @@ export interface MeasurementStatisticsRequirements {
  * @internal
  */
 export interface TaskStatisticsRequirements {
+  /**
+   * Tasks event loop utilization requirements.
+   */
+  readonly elu: MeasurementStatisticsRequirements
   /**
    * Tasks runtime requirements.
    */
@@ -137,10 +141,6 @@ export interface TaskStatisticsRequirements {
    * Tasks wait time requirements.
    */
   readonly waitTime: MeasurementStatisticsRequirements
-  /**
-   * Tasks event loop utilization requirements.
-   */
-  readonly elu: MeasurementStatisticsRequirements
 }
 
 /**
@@ -148,14 +148,14 @@ export interface TaskStatisticsRequirements {
  * @internal
  */
 export interface StrategyPolicy {
-  /**
-   * Expects tasks execution on the newly created dynamic worker.
-   */
-  readonly dynamicWorkerUsage: boolean
   /**
    * Expects the newly created dynamic worker to be flagged as ready.
    */
   readonly dynamicWorkerReady: boolean
+  /**
+   * Expects tasks execution on the newly created dynamic worker.
+   */
+  readonly dynamicWorkerUsage: boolean
 }
 
 /**
@@ -163,25 +163,6 @@ export interface StrategyPolicy {
  * @internal
  */
 export interface IWorkerChoiceStrategy {
-  /**
-   * Strategy policy.
-   */
-  readonly strategyPolicy: StrategyPolicy
-  /**
-   * Tasks statistics requirements.
-   */
-  readonly taskStatisticsRequirements: TaskStatisticsRequirements
-  /**
-   * Resets strategy internals.
-   * @returns `true` if the reset is successful, `false` otherwise.
-   */
-  readonly reset: () => boolean
-  /**
-   * Updates the worker node key strategy internals.
-   * This is called after a task has been executed on a worker node.
-   * @returns `true` if the update is successful, `false` otherwise.
-   */
-  readonly update: (workerNodeKey: number) => boolean
   /**
    * Chooses a worker node in the pool and returns its key.
    * If no worker nodes are not eligible, `undefined` is returned.
@@ -195,9 +176,28 @@ export interface IWorkerChoiceStrategy {
    * @returns `true` if the worker node key is removed, `false` otherwise.
    */
   readonly remove: (workerNodeKey: number) => boolean
+  /**
+   * Resets strategy internals.
+   * @returns `true` if the reset is successful, `false` otherwise.
+   */
+  readonly reset: () => boolean
   /**
    * Sets the worker choice strategy options.
    * @param opts - The worker choice strategy options.
    */
-  readonly setOptions: (opts: WorkerChoiceStrategyOptions | undefined) => void
+  readonly setOptions: (opts: undefined | WorkerChoiceStrategyOptions) => void
+  /**
+   * Strategy policy.
+   */
+  readonly strategyPolicy: StrategyPolicy
+  /**
+   * Tasks statistics requirements.
+   */
+  readonly taskStatisticsRequirements: TaskStatisticsRequirements
+  /**
+   * Updates the worker node key strategy internals.
+   * This is called after a task has been executed on a worker node.
+   * @returns `true` if the update is successful, `false` otherwise.
+   */
+  readonly update: (workerNodeKey: number) => boolean
 }
index 1c0b91b4de0ae9e6ca8a3c93ce11c06fdec39b1b..f4268a0b6a848cd3a8d2fde4bac63a22b515d058 100644 (file)
@@ -2,6 +2,8 @@ import { cpus } from 'node:os'
 
 import type { IPool } from '../pool.js'
 import type { IWorker } from '../worker.js'
+import type { WorkerChoiceStrategiesContext } from './worker-choice-strategies-context.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'
@@ -18,7 +20,6 @@ import {
   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
@@ -93,9 +94,9 @@ export const buildWorkerChoiceStrategyOptions = <
   opts.weights = opts.weights ?? getDefaultWeights(pool.info.maxSize)
   return {
     ...{
+      elu: { median: false },
       runTime: { median: false },
       waitTime: { median: false },
-      elu: { median: false },
     },
     ...opts,
   }
@@ -123,8 +124,8 @@ export const buildWorkerChoiceStrategiesPolicy = (
     ([_, workerChoiceStrategy]) => workerChoiceStrategy.strategyPolicy
   )
   return {
-    dynamicWorkerUsage: policies.some(p => p.dynamicWorkerUsage),
     dynamicWorkerReady: policies.some(p => p.dynamicWorkerReady),
+    dynamicWorkerUsage: policies.some(p => p.dynamicWorkerUsage),
   }
 }
 
@@ -137,6 +138,11 @@ export const buildWorkerChoiceStrategiesTaskStatisticsRequirements = (
       workerChoiceStrategy.taskStatisticsRequirements
   )
   return {
+    elu: {
+      aggregate: taskStatisticsRequirements.some(r => r.elu.aggregate),
+      average: taskStatisticsRequirements.some(r => r.elu.average),
+      median: taskStatisticsRequirements.some(r => r.elu.median),
+    },
     runTime: {
       aggregate: taskStatisticsRequirements.some(r => r.runTime.aggregate),
       average: taskStatisticsRequirements.some(r => r.runTime.average),
@@ -147,11 +153,6 @@ export const buildWorkerChoiceStrategiesTaskStatisticsRequirements = (
       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),
-    },
   }
 }
 
@@ -162,25 +163,25 @@ export const getWorkerChoiceStrategy = <Worker extends IWorker, 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.FAIR_SHARE:
+      return new (FairShareWorkerChoiceStrategy.bind(context))(pool, opts)
+    case WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN:
+      return new (InterleavedWeightedRoundRobinWorkerChoiceStrategy.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.LEAST_USED:
+      return new (LeastUsedWorkerChoiceStrategy.bind(context))(pool, opts)
+    case WorkerChoiceStrategies.ROUND_ROBIN:
+      return new (RoundRobinWorkerChoiceStrategy.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
index ffd4fc39ca2a99fbb9f74044892e0949653d5c42..3cb062722e51c9db6f221d743559b50886c8d0b2 100644 (file)
@@ -1,13 +1,14 @@
 import type { IPool } from '../pool.js'
-import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
 import type { IWorker } from '../worker.js'
-import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
 import type {
   IWorkerChoiceStrategy,
   TaskStatisticsRequirements,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
 
+import { DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS } from '../utils.js'
+import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy.js'
+
 /**
  * Selects the next worker with a weighted round robin scheduling algorithm.
  * Loosely modeled after the weighted round robin queueing algorithm: https://en.wikipedia.org/wiki/Weighted_round_robin.
@@ -22,8 +23,14 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
   >
   extends AbstractWorkerChoiceStrategy<Worker, Data, Response>
   implements IWorkerChoiceStrategy {
+  /**
+   * Worker node virtual execution time.
+   */
+  private workerNodeVirtualTaskExecutionTime = 0
+
   /** @inheritDoc */
   public readonly taskStatisticsRequirements: TaskStatisticsRequirements = {
+    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
     runTime: {
       aggregate: true,
       average: true,
@@ -34,14 +41,8 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
       average: true,
       median: false,
     },
-    elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
   }
 
-  /**
-   * Worker node virtual execution time.
-   */
-  private workerNodeVirtualTaskExecutionTime = 0
-
   /** @inheritDoc */
   public constructor (
     pool: IPool<Worker, Data, Response>,
@@ -51,16 +52,26 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
-  /** @inheritDoc */
-  public reset (): boolean {
-    this.resetWorkerNodeKeyProperties()
-    this.workerNodeVirtualTaskExecutionTime = 0
-    return true
-  }
-
-  /** @inheritDoc */
-  public update (): boolean {
-    return true
+  private weightedRoundRobinNextWorkerNodeKey (): number | undefined {
+    const workerWeight =
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.opts!.weights![this.nextWorkerNodeKey ?? this.previousWorkerNodeKey]
+    if (this.workerNodeVirtualTaskExecutionTime < workerWeight) {
+      this.workerNodeVirtualTaskExecutionTime +=
+        this.getWorkerNodeTaskWaitTime(
+          this.nextWorkerNodeKey ?? this.previousWorkerNodeKey
+        ) +
+        this.getWorkerNodeTaskRunTime(
+          this.nextWorkerNodeKey ?? this.previousWorkerNodeKey
+        )
+    } else {
+      this.nextWorkerNodeKey =
+        this.nextWorkerNodeKey === this.pool.workerNodes.length - 1
+          ? 0
+          : (this.nextWorkerNodeKey ?? this.previousWorkerNodeKey) + 1
+      this.workerNodeVirtualTaskExecutionTime = 0
+    }
+    return this.nextWorkerNodeKey
   }
 
   /** @inheritDoc */
@@ -92,25 +103,15 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
     return true
   }
 
-  private weightedRoundRobinNextWorkerNodeKey (): number | undefined {
-    const workerWeight =
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.opts!.weights![this.nextWorkerNodeKey ?? this.previousWorkerNodeKey]
-    if (this.workerNodeVirtualTaskExecutionTime < workerWeight) {
-      this.workerNodeVirtualTaskExecutionTime +=
-        this.getWorkerNodeTaskWaitTime(
-          this.nextWorkerNodeKey ?? this.previousWorkerNodeKey
-        ) +
-        this.getWorkerNodeTaskRunTime(
-          this.nextWorkerNodeKey ?? this.previousWorkerNodeKey
-        )
-    } else {
-      this.nextWorkerNodeKey =
-        this.nextWorkerNodeKey === this.pool.workerNodes.length - 1
-          ? 0
-          : (this.nextWorkerNodeKey ?? this.previousWorkerNodeKey) + 1
-      this.workerNodeVirtualTaskExecutionTime = 0
-    }
-    return this.nextWorkerNodeKey
+  /** @inheritDoc */
+  public reset (): boolean {
+    this.resetWorkerNodeKeyProperties()
+    this.workerNodeVirtualTaskExecutionTime = 0
+    return true
+  }
+
+  /** @inheritDoc */
+  public update (): boolean {
+    return true
   }
 }
index 650242120814e05898f550d0af0414df8e56bd5d..a1ccb4e96277c21895a868e231c0941a030a6371 100644 (file)
@@ -7,6 +7,7 @@ import type {
   WorkerChoiceStrategy,
   WorkerChoiceStrategyOptions,
 } from './selection-strategies-types.js'
+
 import { WorkerChoiceStrategies } from './selection-strategies-types.js'
 import {
   buildWorkerChoiceStrategiesPolicy,
@@ -28,14 +29,14 @@ export class WorkerChoiceStrategiesContext<
   Response = unknown
 > {
   /**
-   * The number of worker choice strategies execution retries.
+   * The default worker choice strategy in the context.
    */
-  public retriesCount: number
+  private defaultWorkerChoiceStrategy: WorkerChoiceStrategy
 
   /**
-   * The default worker choice strategy in the context.
+   * The maximum number of worker choice strategies execution retries.
    */
-  private defaultWorkerChoiceStrategy: WorkerChoiceStrategy
+  private readonly retries: number
 
   /**
    * The worker choice strategies registered in the context.
@@ -56,9 +57,9 @@ export class WorkerChoiceStrategiesContext<
   private workerChoiceStrategiesTaskStatisticsRequirements: TaskStatisticsRequirements
 
   /**
-   * The maximum number of worker choice strategies execution retries.
+   * The number of worker choice strategies execution retries.
    */
-  private readonly retries: number
+  public retriesCount: number
 
   /**
    * Worker choice strategies context constructor.
@@ -97,62 +98,29 @@ export class WorkerChoiceStrategiesContext<
   }
 
   /**
-   * 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.
+   * Adds a worker choice strategy to the context.
+   * @param workerChoiceStrategy - The worker choice strategy to add.
+   * @param pool - The pool instance.
    * @param opts - The worker choice strategy options.
+   * @returns The worker choice strategies.
    */
-  public setDefaultWorkerChoiceStrategy (
+  private addWorkerChoiceStrategy (
     workerChoiceStrategy: WorkerChoiceStrategy,
+    pool: IPool<Worker, Data, Response>,
     opts?: WorkerChoiceStrategyOptions
-  ): void {
-    if (workerChoiceStrategy !== this.defaultWorkerChoiceStrategy) {
-      this.defaultWorkerChoiceStrategy = workerChoiceStrategy
-      this.addWorkerChoiceStrategy(workerChoiceStrategy, this.pool, opts)
+  ): Map<WorkerChoiceStrategy, IWorkerChoiceStrategy> {
+    if (!this.workerChoiceStrategies.has(workerChoiceStrategy)) {
+      return this.workerChoiceStrategies.set(
+        workerChoiceStrategy,
+        getWorkerChoiceStrategy<Worker, Data, Response>(
+          workerChoiceStrategy,
+          pool,
+          this,
+          opts
+        )
+      )
     }
-  }
-
-  /**
-   * Updates the worker node key in the active worker choice strategies in the context internals.
-   * @param workerNodeKey - The worker node key.
-   * @returns `true` if the update is successful, `false` otherwise.
-   */
-  public update (workerNodeKey: number): boolean {
-    return Array.from(
-      this.workerChoiceStrategies,
-      ([_, workerChoiceStrategy]) => workerChoiceStrategy.update(workerNodeKey)
-    ).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)!
-    )
+    return this.workerChoiceStrategies
   }
 
   /**
@@ -181,6 +149,49 @@ export class WorkerChoiceStrategiesContext<
     return workerNodeKey
   }
 
+  /**
+   * 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)
+  }
+
+  /**
+   * 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)!
+    )
+  }
+
+  /**
+   * 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
+  }
+
   /**
    * Removes the worker node key from the active worker choice strategies in the context.
    * @param workerNodeKey - The worker node key.
@@ -193,11 +204,26 @@ export class WorkerChoiceStrategiesContext<
     ).every(r => r)
   }
 
+  /**
+   * 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)
+    }
+  }
+
   /**
    * Sets the active worker choice strategies in the context options.
    * @param opts - The worker choice strategy options.
    */
-  public setOptions (opts: WorkerChoiceStrategyOptions | undefined): void {
+  public setOptions (opts: undefined | WorkerChoiceStrategyOptions): void {
     for (const workerChoiceStrategy of this.workerChoiceStrategies.values()) {
       workerChoiceStrategy.setOptions(opts)
     }
@@ -232,39 +258,14 @@ export class WorkerChoiceStrategiesContext<
   }
 
   /**
-   * Adds a worker choice strategy to the context.
-   * @param workerChoiceStrategy - The worker choice strategy to add.
-   * @param pool - The pool instance.
-   * @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.
+   * Updates the worker node key in the active worker choice strategies in the context internals.
+   * @param workerNodeKey - The worker node key.
+   * @returns `true` if the update is successful, `false` otherwise.
    */
-  private removeWorkerChoiceStrategy (
-    workerChoiceStrategy: WorkerChoiceStrategy
-  ): boolean {
-    return this.workerChoiceStrategies.delete(workerChoiceStrategy)
+  public update (workerNodeKey: number): boolean {
+    return Array.from(
+      this.workerChoiceStrategies,
+      ([_, workerChoiceStrategy]) => workerChoiceStrategy.update(workerNodeKey)
+    ).every(r => r)
   }
 }
index 0d088da1489fa04b1c9df45540d4cb44431e5d32..fc4115bef8066e3236ef7550ae5e9e33c1f40204 100644 (file)
@@ -36,11 +36,6 @@ export class DynamicThreadPool<
     )
   }
 
-  /** @inheritDoc */
-  protected shallCreateDynamicWorker (): boolean {
-    return (!this.full && this.internalBusy()) || this.empty
-  }
-
   /** @inheritDoc */
   protected checkAndEmitDynamicWorkerCreationEvents (): void {
     if (this.full) {
@@ -49,12 +44,17 @@ export class DynamicThreadPool<
   }
 
   /** @inheritDoc */
-  protected get type (): PoolType {
-    return PoolTypes.dynamic
+  protected shallCreateDynamicWorker (): boolean {
+    return (!this.full && this.internalBusy()) || this.empty
   }
 
   /** @inheritDoc */
   protected get busy (): boolean {
     return this.full && this.internalBusy()
   }
+
+  /** @inheritDoc */
+  protected get type (): PoolType {
+    return PoolTypes.dynamic
+  }
 }
index 69b3c3071e487abf1658c454289c7117ee351152..f65a0cd557fce10e6a99bad38a5f5d605db8631c 100644 (file)
@@ -5,6 +5,7 @@ import {
 } from 'node:worker_threads'
 
 import type { MessageValue } from '../../utility-types.js'
+
 import { AbstractPool } from '../abstract-pool.js'
 import { type PoolOptions, type PoolType, PoolTypes } from '../pool.js'
 import { type WorkerType, WorkerTypes } from '../worker.js'
@@ -42,70 +43,75 @@ export class FixedThreadPool<
   }
 
   /** @inheritDoc */
-  protected isMain (): boolean {
-    return isMainThread
+  protected checkAndEmitDynamicWorkerCreationEvents (): void {
+    /* noop */
   }
 
   /** @inheritDoc */
-  protected sendToWorker (
+  protected deregisterWorkerMessageListener<Message extends Data | Response>(
     workerNodeKey: number,
-    message: MessageValue<Data>,
-    transferList?: readonly TransferListItem[]
+    listener: (message: MessageValue<Message>) => void
   ): void {
-    this.workerNodes[workerNodeKey]?.messageChannel?.port1.postMessage(
-      {
-        ...message,
-        workerId: this.getWorkerInfo(workerNodeKey)?.id,
-      } satisfies MessageValue<Data>,
-      transferList
+    this.workerNodes[workerNodeKey].messageChannel?.port1.off(
+      'message',
+      listener
     )
   }
 
   /** @inheritDoc */
-  protected sendStartupMessageToWorker (workerNodeKey: number): void {
-    const workerNode = this.workerNodes[workerNodeKey]
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const port2 = workerNode.messageChannel!.port2
-    workerNode.worker.postMessage(
-      {
-        ready: false,
-        workerId: this.getWorkerInfo(workerNodeKey)?.id,
-        port: port2,
-      } satisfies MessageValue<Data>,
-      [port2]
-    )
+  protected isMain (): boolean {
+    return isMainThread
   }
 
   /** @inheritDoc */
-  protected registerWorkerMessageListener<Message extends Data | Response>(
+  protected registerOnceWorkerMessageListener<Message extends Data | Response>(
     workerNodeKey: number,
     listener: (message: MessageValue<Message>) => void
   ): void {
-    this.workerNodes[workerNodeKey].messageChannel?.port1.on(
+    this.workerNodes[workerNodeKey].messageChannel?.port1.once(
       'message',
       listener
     )
   }
 
   /** @inheritDoc */
-  protected registerOnceWorkerMessageListener<Message extends Data | Response>(
+  protected registerWorkerMessageListener<Message extends Data | Response>(
     workerNodeKey: number,
     listener: (message: MessageValue<Message>) => void
   ): void {
-    this.workerNodes[workerNodeKey].messageChannel?.port1.once(
+    this.workerNodes[workerNodeKey].messageChannel?.port1.on(
       'message',
       listener
     )
   }
 
   /** @inheritDoc */
-  protected deregisterWorkerMessageListener<Message extends Data | Response>(
+  protected sendStartupMessageToWorker (workerNodeKey: number): void {
+    const workerNode = this.workerNodes[workerNodeKey]
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const port2 = workerNode.messageChannel!.port2
+    workerNode.worker.postMessage(
+      {
+        port: port2,
+        ready: false,
+        workerId: this.getWorkerInfo(workerNodeKey)?.id,
+      } satisfies MessageValue<Data>,
+      [port2]
+    )
+  }
+
+  /** @inheritDoc */
+  protected sendToWorker (
     workerNodeKey: number,
-    listener: (message: MessageValue<Message>) => void
+    message: MessageValue<Data>,
+    transferList?: readonly TransferListItem[]
   ): void {
-    this.workerNodes[workerNodeKey].messageChannel?.port1.off(
-      'message',
-      listener
+    this.workerNodes[workerNodeKey]?.messageChannel?.port1.postMessage(
+      {
+        ...message,
+        workerId: this.getWorkerInfo(workerNodeKey)?.id,
+      } satisfies MessageValue<Data>,
+      transferList
     )
   }
 
@@ -115,8 +121,8 @@ export class FixedThreadPool<
   }
 
   /** @inheritDoc */
-  protected checkAndEmitDynamicWorkerCreationEvents (): void {
-    /* noop */
+  protected get busy (): boolean {
+    return this.internalBusy()
   }
 
   /** @inheritDoc */
@@ -128,9 +134,4 @@ export class FixedThreadPool<
   protected get worker (): WorkerType {
     return WorkerTypes.thread
   }
-
-  /** @inheritDoc */
-  protected get busy (): boolean {
-    return this.internalBusy()
-  }
 }
index 7b3c21fa01a4902a44c350c7dfd0ced4af6c89ef..3c88dd4588c2cdfcffc8f44eaa5aa31cfb4937e5 100644 (file)
@@ -8,14 +8,15 @@ import {
 } from 'node:worker_threads'
 
 import type { MessageValue, Task } from '../utility-types.js'
-import { average, isPlainObject, max, median, min } from '../utils.js'
 import type { TasksQueueOptions } from './pool.js'
+import type { WorkerChoiceStrategiesContext } from './selection-strategies/worker-choice-strategies-context.js'
+
+import { average, isPlainObject, max, median, min } from '../utils.js'
 import {
   type MeasurementStatisticsRequirements,
   WorkerChoiceStrategies,
   type WorkerChoiceStrategy,
 } from './selection-strategies/selection-strategies-types.js'
-import type { WorkerChoiceStrategiesContext } from './selection-strategies/worker-choice-strategies-context.js'
 import {
   type IWorker,
   type IWorkerNode,
@@ -40,12 +41,12 @@ export const getDefaultTasksQueueOptions = (
   poolMaxSize: number
 ): Required<TasksQueueOptions> => {
   return {
-    size: Math.pow(poolMaxSize, 2),
     concurrency: 1,
-    taskStealing: true,
+    size: Math.pow(poolMaxSize, 2),
+    tasksFinishedTimeout: 2000,
     tasksStealingOnBackPressure: true,
     tasksStealingRatio: 0.6,
-    tasksFinishedTimeout: 2000,
+    taskStealing: true,
   }
 }
 
@@ -102,7 +103,7 @@ export const checkValidPriority = (priority: number | undefined): void => {
 }
 
 export const checkValidWorkerChoiceStrategy = (
-  workerChoiceStrategy: WorkerChoiceStrategy | undefined
+  workerChoiceStrategy: undefined | WorkerChoiceStrategy
 ): void => {
   if (
     workerChoiceStrategy != null &&
@@ -167,9 +168,9 @@ export const checkValidTasksQueueOptions = (
 }
 
 export const checkWorkerNodeArguments = (
-  type: WorkerType | undefined,
+  type: undefined | WorkerType,
   filePath: string | undefined,
-  opts: WorkerNodeOptions | undefined
+  opts: undefined | WorkerNodeOptions
 ): void => {
   if (type == null) {
     throw new TypeError('Cannot construct a worker node without a worker type')
@@ -289,8 +290,8 @@ export const updateWaitTimeWorkerUsage = <
   Response = unknown
 >(
     workerChoiceStrategiesContext:
-    | WorkerChoiceStrategiesContext<Worker, Data, Response>
-    | undefined,
+    | undefined
+    | WorkerChoiceStrategiesContext<Worker, Data, Response>,
     workerUsage: WorkerUsage,
     task: Task<Data>
   ): void => {
@@ -328,8 +329,8 @@ export const updateRunTimeWorkerUsage = <
   Response = unknown
 >(
     workerChoiceStrategiesContext:
-    | WorkerChoiceStrategiesContext<Worker, Data, Response>
-    | undefined,
+    | undefined
+    | WorkerChoiceStrategiesContext<Worker, Data, Response>,
     workerUsage: WorkerUsage,
     message: MessageValue<Response>
   ): void => {
@@ -349,8 +350,8 @@ export const updateEluWorkerUsage = <
   Response = unknown
 >(
     workerChoiceStrategiesContext:
-    | WorkerChoiceStrategiesContext<Worker, Data, Response>
-    | undefined,
+    | undefined
+    | WorkerChoiceStrategiesContext<Worker, Data, Response>,
     workerUsage: WorkerUsage,
     message: MessageValue<Response>
   ): void => {
@@ -390,13 +391,13 @@ export const createWorker = <Worker extends IWorker>(
   opts: { env?: Record<string, unknown>; workerOptions?: WorkerOptions }
 ): Worker => {
   switch (type) {
+    case WorkerTypes.cluster:
+      return cluster.fork(opts.env) as unknown as Worker
     case WorkerTypes.thread:
       return new ThreadWorker(filePath, {
         env: SHARE_ENV,
         ...opts.workerOptions,
       }) as unknown as Worker
-    case WorkerTypes.cluster:
-      return cluster.fork(opts.env) as unknown as Worker
     default:
       // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
       throw new Error(`Unknown worker type '${type}'`)
@@ -409,7 +410,7 @@ export const createWorker = <Worker extends IWorker>(
  * @returns The worker type of the given worker.
  * @internal
  */
-export const getWorkerType = (worker: IWorker): WorkerType | undefined => {
+export const getWorkerType = (worker: IWorker): undefined | WorkerType => {
   if (worker instanceof ThreadWorker) {
     return WorkerTypes.thread
   } else if (worker instanceof ClusterWorker) {
@@ -447,8 +448,8 @@ export const waitWorkerNodeEvents = async <
       return
     }
     switch (workerNodeEvent) {
-      case 'idle':
       case 'backPressure':
+      case 'idle':
       case 'taskFinished':
         workerNode.on(workerNodeEvent, () => {
           ++events
index 8748b414cd7fe0e70c1303c90ee970a114d0a1e0..b29601b303b40d5023b9a4afcaaafce782b1dfb4 100644 (file)
@@ -1,9 +1,10 @@
 import { EventEmitter } from 'node:events'
 import { MessageChannel } from 'node:worker_threads'
 
+import type { Task } from '../utility-types.js'
+
 import { CircularBuffer } from '../circular-buffer.js'
 import { PriorityQueue } from '../queues/priority-queue.js'
-import type { Task } from '../utility-types.js'
 import { DEFAULT_TASK_NAME } from '../utils.js'
 import {
   checkWorkerNodeArguments,
@@ -32,21 +33,21 @@ import {
 export class WorkerNode<Worker extends IWorker, Data = unknown>
   extends EventEmitter
   implements IWorkerNode<Worker, Data> {
-  /** @inheritdoc */
-  public readonly worker: Worker
+  private setBackPressureFlag: boolean
+  private readonly taskFunctionsUsage: Map<string, WorkerUsage>
+  private readonly tasksQueue: PriorityQueue<Task<Data>>
   /** @inheritdoc */
   public readonly info: WorkerInfo
   /** @inheritdoc */
-  public usage: WorkerUsage
+  public messageChannel?: MessageChannel
   /** @inheritdoc */
   public strategyData?: StrategyData
   /** @inheritdoc */
-  public messageChannel?: MessageChannel
-  /** @inheritdoc */
   public tasksQueueBackPressureSize: number
-  private readonly tasksQueue: PriorityQueue<Task<Data>>
-  private setBackPressureFlag: boolean
-  private readonly taskFunctionsUsage: Map<string, WorkerUsage>
+  /** @inheritdoc */
+  public usage: WorkerUsage
+  /** @inheritdoc */
+  public readonly worker: Worker
 
   /**
    * Constructs a new worker node.
@@ -76,30 +77,126 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
     this.taskFunctionsUsage = new Map<string, WorkerUsage>()
   }
 
+  private closeMessageChannel (): void {
+    if (this.messageChannel != null) {
+      this.messageChannel.port1.unref()
+      this.messageChannel.port2.unref()
+      this.messageChannel.port1.close()
+      this.messageChannel.port2.close()
+      delete this.messageChannel
+    }
+  }
+
+  private initTaskFunctionWorkerUsage (name: string): WorkerUsage {
+    const getTaskFunctionQueueSize = (): number => {
+      let taskFunctionQueueSize = 0
+      for (const task of this.tasksQueue) {
+        if (
+          (task.name === DEFAULT_TASK_NAME &&
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            name === this.info.taskFunctionsProperties![1].name) ||
+          (task.name !== DEFAULT_TASK_NAME && name === task.name)
+        ) {
+          ++taskFunctionQueueSize
+        }
+      }
+      return taskFunctionQueueSize
+    }
+    return {
+      elu: {
+        active: {
+          history: new CircularBuffer(MeasurementHistorySize),
+        },
+        idle: {
+          history: new CircularBuffer(MeasurementHistorySize),
+        },
+      },
+      runTime: {
+        history: new CircularBuffer(MeasurementHistorySize),
+      },
+      tasks: {
+        executed: 0,
+        executing: 0,
+        failed: 0,
+        get queued (): number {
+          return getTaskFunctionQueueSize()
+        },
+        sequentiallyStolen: 0,
+        stolen: 0,
+      },
+      waitTime: {
+        history: new CircularBuffer(MeasurementHistorySize),
+      },
+    }
+  }
+
+  private initWorkerInfo (worker: Worker): WorkerInfo {
+    return {
+      backPressure: false,
+      backPressureStealing: false,
+      continuousStealing: false,
+      dynamic: false,
+      id: getWorkerId(worker),
+      ready: false,
+      stealing: false,
+      stolen: false,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      type: getWorkerType(worker)!,
+    }
+  }
+
+  private initWorkerUsage (): WorkerUsage {
+    const getTasksQueueSize = (): number => {
+      return this.tasksQueue.size
+    }
+    const getTasksQueueMaxSize = (): number => {
+      return this.tasksQueue.maxSize
+    }
+    return {
+      elu: {
+        active: {
+          history: new CircularBuffer(MeasurementHistorySize),
+        },
+        idle: {
+          history: new CircularBuffer(MeasurementHistorySize),
+        },
+      },
+      runTime: {
+        history: new CircularBuffer(MeasurementHistorySize),
+      },
+      tasks: {
+        executed: 0,
+        executing: 0,
+        failed: 0,
+        get maxQueued (): number {
+          return getTasksQueueMaxSize()
+        },
+        get queued (): number {
+          return getTasksQueueSize()
+        },
+        sequentiallyStolen: 0,
+        stolen: 0,
+      },
+      waitTime: {
+        history: new CircularBuffer(MeasurementHistorySize),
+      },
+    }
+  }
+
   /** @inheritdoc */
-  public setTasksQueuePriority (enablePriority: boolean): void {
-    this.tasksQueue.enablePriority = enablePriority
+  public clearTasksQueue (): void {
+    this.tasksQueue.clear()
   }
 
   /** @inheritdoc */
-  public tasksQueueSize (): number {
-    return this.tasksQueue.size
+  public deleteTaskFunctionWorkerUsage (name: string): boolean {
+    return this.taskFunctionsUsage.delete(name)
   }
 
   /** @inheritdoc */
-  public enqueueTask (task: Task<Data>): number {
-    const tasksQueueSize = this.tasksQueue.enqueue(task, task.priority)
-    if (
-      !this.setBackPressureFlag &&
-      this.hasBackPressure() &&
-      !this.info.backPressure
-    ) {
-      this.setBackPressureFlag = true
-      this.info.backPressure = true
-      this.emit('backPressure', { workerId: this.info.id })
-      this.setBackPressureFlag = false
-    }
-    return tasksQueueSize
+  public dequeueLastPrioritizedTask (): Task<Data> | undefined {
+    // Start from the last empty or partially filled bucket
+    return this.dequeueTask(this.tasksQueue.buckets + 1)
   }
 
   /** @inheritdoc */
@@ -118,63 +215,23 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
   }
 
   /** @inheritdoc */
-  public dequeueLastPrioritizedTask (): Task<Data> | undefined {
-    // Start from the last empty or partially filled bucket
-    return this.dequeueTask(this.tasksQueue.buckets + 1)
-  }
-
-  /** @inheritdoc */
-  public clearTasksQueue (): void {
-    this.tasksQueue.clear()
-  }
-
-  /** @inheritdoc */
-  public hasBackPressure (): boolean {
-    return this.tasksQueue.size >= this.tasksQueueBackPressureSize
-  }
-
-  /** @inheritdoc */
-  public async terminate (): Promise<void> {
-    const waitWorkerExit = new Promise<void>(resolve => {
-      this.registerOnceWorkerEventHandler('exit', () => {
-        resolve()
-      })
-    })
-    this.closeMessageChannel()
-    this.removeAllListeners()
-    switch (this.info.type) {
-      case WorkerTypes.thread:
-        this.worker.unref?.()
-        await this.worker.terminate?.()
-        break
-      case WorkerTypes.cluster:
-        this.registerOnceWorkerEventHandler('disconnect', () => {
-          this.worker.kill?.()
-        })
-        this.worker.disconnect?.()
-        break
+  public enqueueTask (task: Task<Data>): number {
+    const tasksQueueSize = this.tasksQueue.enqueue(task, task.priority)
+    if (
+      !this.setBackPressureFlag &&
+      this.hasBackPressure() &&
+      !this.info.backPressure
+    ) {
+      this.setBackPressureFlag = true
+      this.info.backPressure = true
+      this.emit('backPressure', { workerId: this.info.id })
+      this.setBackPressureFlag = false
     }
-    await waitWorkerExit
-  }
-
-  /** @inheritdoc */
-  public registerWorkerEventHandler (
-    event: string,
-    handler: EventHandler<Worker>
-  ): void {
-    this.worker.on(event, handler)
-  }
-
-  /** @inheritdoc */
-  public registerOnceWorkerEventHandler (
-    event: string,
-    handler: EventHandler<Worker>
-  ): void {
-    this.worker.once(event, handler)
+    return tasksQueueSize
   }
 
   /** @inheritdoc */
-  public getTaskFunctionWorkerUsage (name: string): WorkerUsage | undefined {
+  public getTaskFunctionWorkerUsage (name: string): undefined | WorkerUsage {
     if (!Array.isArray(this.info.taskFunctionsProperties)) {
       throw new Error(
         `Cannot get task function worker usage for task function name '${name}' when task function properties list is not yet defined`
@@ -198,113 +255,57 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
   }
 
   /** @inheritdoc */
-  public deleteTaskFunctionWorkerUsage (name: string): boolean {
-    return this.taskFunctionsUsage.delete(name)
+  public hasBackPressure (): boolean {
+    return this.tasksQueue.size >= this.tasksQueueBackPressureSize
   }
 
-  private closeMessageChannel (): void {
-    if (this.messageChannel != null) {
-      this.messageChannel.port1.unref()
-      this.messageChannel.port2.unref()
-      this.messageChannel.port1.close()
-      this.messageChannel.port2.close()
-      delete this.messageChannel
-    }
+  /** @inheritdoc */
+  public registerOnceWorkerEventHandler (
+    event: string,
+    handler: EventHandler<Worker>
+  ): void {
+    this.worker.once(event, handler)
   }
 
-  private initWorkerInfo (worker: Worker): WorkerInfo {
-    return {
-      id: getWorkerId(worker),
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      type: getWorkerType(worker)!,
-      dynamic: false,
-      ready: false,
-      stealing: false,
-      stolen: false,
-      continuousStealing: false,
-      backPressureStealing: false,
-      backPressure: false,
-    }
+  /** @inheritdoc */
+  public registerWorkerEventHandler (
+    event: string,
+    handler: EventHandler<Worker>
+  ): void {
+    this.worker.on(event, handler)
   }
 
-  private initWorkerUsage (): WorkerUsage {
-    const getTasksQueueSize = (): number => {
-      return this.tasksQueue.size
-    }
-    const getTasksQueueMaxSize = (): number => {
-      return this.tasksQueue.maxSize
-    }
-    return {
-      tasks: {
-        executed: 0,
-        executing: 0,
-        get queued (): number {
-          return getTasksQueueSize()
-        },
-        get maxQueued (): number {
-          return getTasksQueueMaxSize()
-        },
-        sequentiallyStolen: 0,
-        stolen: 0,
-        failed: 0,
-      },
-      runTime: {
-        history: new CircularBuffer(MeasurementHistorySize),
-      },
-      waitTime: {
-        history: new CircularBuffer(MeasurementHistorySize),
-      },
-      elu: {
-        idle: {
-          history: new CircularBuffer(MeasurementHistorySize),
-        },
-        active: {
-          history: new CircularBuffer(MeasurementHistorySize),
-        },
-      },
-    }
+  /** @inheritdoc */
+  public setTasksQueuePriority (enablePriority: boolean): void {
+    this.tasksQueue.enablePriority = enablePriority
   }
 
-  private initTaskFunctionWorkerUsage (name: string): WorkerUsage {
-    const getTaskFunctionQueueSize = (): number => {
-      let taskFunctionQueueSize = 0
-      for (const task of this.tasksQueue) {
-        if (
-          (task.name === DEFAULT_TASK_NAME &&
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            name === this.info.taskFunctionsProperties![1].name) ||
-          (task.name !== DEFAULT_TASK_NAME && name === task.name)
-        ) {
-          ++taskFunctionQueueSize
-        }
-      }
-      return taskFunctionQueueSize
-    }
-    return {
-      tasks: {
-        executed: 0,
-        executing: 0,
-        get queued (): number {
-          return getTaskFunctionQueueSize()
-        },
-        sequentiallyStolen: 0,
-        stolen: 0,
-        failed: 0,
-      },
-      runTime: {
-        history: new CircularBuffer(MeasurementHistorySize),
-      },
-      waitTime: {
-        history: new CircularBuffer(MeasurementHistorySize),
-      },
-      elu: {
-        idle: {
-          history: new CircularBuffer(MeasurementHistorySize),
-        },
-        active: {
-          history: new CircularBuffer(MeasurementHistorySize),
-        },
-      },
+  /** @inheritdoc */
+  public tasksQueueSize (): number {
+    return this.tasksQueue.size
+  }
+
+  /** @inheritdoc */
+  public async terminate (): Promise<void> {
+    const waitWorkerExit = new Promise<void>(resolve => {
+      this.registerOnceWorkerEventHandler('exit', () => {
+        resolve()
+      })
+    })
+    this.closeMessageChannel()
+    this.removeAllListeners()
+    switch (this.info.type) {
+      case WorkerTypes.thread:
+        this.worker.unref?.()
+        await this.worker.terminate?.()
+        break
+      case WorkerTypes.cluster:
+        this.registerOnceWorkerEventHandler('disconnect', () => {
+          this.worker.kill?.()
+        })
+        this.worker.disconnect?.()
+        break
     }
+    await waitWorkerExit
   }
 }
index 3c4fe610e101cf684d71cff095a7a28fb477d15f..f67ec2f6501fc927290555018e4d9f1e0ca4b7a0 100644 (file)
@@ -42,10 +42,10 @@ export type ExitHandler<Worker extends IWorker> = (
  * @typeParam Worker - Type of worker.
  */
 export type EventHandler<Worker extends IWorker> =
-  | OnlineHandler<Worker>
-  | MessageHandler<Worker>
   | ErrorHandler<Worker>
   | ExitHandler<Worker>
+  | MessageHandler<Worker>
+  | OnlineHandler<Worker>
 
 /**
  * Measurement history size.
@@ -62,25 +62,25 @@ export interface MeasurementStatistics {
    */
   aggregate?: number
   /**
-   * Measurement minimum.
+   * Measurement average.
    */
-  minimum?: number
+  average?: number
   /**
-   * Measurement maximum.
+   * Measurement history.
    */
-  maximum?: number
+  readonly history: CircularBuffer
   /**
-   * Measurement average.
+   * Measurement maximum.
    */
-  average?: number
+  maximum?: number
   /**
    * Measurement median.
    */
   median?: number
   /**
-   * Measurement history.
+   * Measurement minimum.
    */
-  readonly history: CircularBuffer
+  minimum?: number
 }
 
 /**
@@ -88,8 +88,8 @@ export interface MeasurementStatistics {
  * @internal
  */
 export interface EventLoopUtilizationMeasurementStatistics {
-  readonly idle: MeasurementStatistics
   readonly active: MeasurementStatistics
+  readonly idle: MeasurementStatistics
   utilization?: number
 }
 
@@ -107,13 +107,17 @@ export interface TaskStatistics {
    */
   executing: number
   /**
-   * Number of queued tasks.
+   * Number of failed tasks.
    */
-  readonly queued: number
+  failed: number
   /**
    * Maximum number of queued tasks.
    */
   readonly maxQueued?: number
+  /**
+   * Number of queued tasks.
+   */
+  readonly queued: number
   /**
    * Number of sequentially stolen tasks.
    */
@@ -122,19 +126,15 @@ export interface TaskStatistics {
    * Number of stolen tasks.
    */
   stolen: number
-  /**
-   * Number of failed tasks.
-   */
-  failed: number
 }
 
 /**
  * Enumeration of worker types.
  */
-export const WorkerTypes: Readonly<{ thread: 'thread'; cluster: 'cluster' }> =
+export const WorkerTypes: Readonly<{ cluster: 'cluster'; thread: 'thread' }> =
   Object.freeze({
-    thread: 'thread',
     cluster: 'cluster',
+    thread: 'thread',
   } as const)
 
 /**
@@ -148,17 +148,28 @@ export type WorkerType = keyof typeof WorkerTypes
  */
 export interface WorkerInfo {
   /**
-   * Worker id.
+   * Back pressure flag.
+   * This flag is set to `true` when worker node tasks queue has back pressure.
    */
-  readonly id: number | undefined
+  backPressure: boolean
   /**
-   * Worker type.
+   * Back pressure stealing flag.
+   * This flag is set to `true` when worker node is stealing one task from another back pressured worker node.
    */
-  readonly type: WorkerType
+  backPressureStealing: boolean
+  /**
+   * Continuous stealing flag.
+   * This flag is set to `true` when worker node is continuously stealing tasks from other worker nodes.
+   */
+  continuousStealing: boolean
   /**
    * Dynamic flag.
    */
   dynamic: boolean
+  /**
+   * Worker id.
+   */
+  readonly id: number | undefined
   /**
    * Ready flag.
    */
@@ -173,25 +184,14 @@ export interface WorkerInfo {
    * This flag is set to `true` when worker node has one task stolen from another worker node.
    */
   stolen: boolean
-  /**
-   * Continuous stealing flag.
-   * This flag is set to `true` when worker node is continuously stealing tasks from other worker nodes.
-   */
-  continuousStealing: boolean
-  /**
-   * Back pressure stealing flag.
-   * This flag is set to `true` when worker node is stealing one task from another back pressured worker node.
-   */
-  backPressureStealing: boolean
-  /**
-   * Back pressure flag.
-   * This flag is set to `true` when worker node tasks queue has back pressure.
-   */
-  backPressure: boolean
   /**
    * Task functions properties.
    */
   taskFunctionsProperties?: TaskFunctionProperties[]
+  /**
+   * Worker type.
+   */
+  readonly type: WorkerType
 }
 
 /**
@@ -200,21 +200,21 @@ export interface WorkerInfo {
  */
 export interface WorkerUsage {
   /**
-   * Tasks statistics.
+   * Tasks event loop utilization statistics.
    */
-  readonly tasks: TaskStatistics
+  readonly elu: EventLoopUtilizationMeasurementStatistics
   /**
    * Tasks runtime statistics.
    */
   readonly runTime: MeasurementStatistics
   /**
-   * Tasks wait time statistics.
+   * Tasks statistics.
    */
-  readonly waitTime: MeasurementStatistics
+  readonly tasks: TaskStatistics
   /**
-   * Tasks event loop utilization statistics.
+   * Tasks wait time statistics.
    */
-  readonly elu: EventLoopUtilizationMeasurementStatistics
+  readonly waitTime: MeasurementStatistics
 }
 
 /**
@@ -229,14 +229,18 @@ export interface StrategyData {
  * Worker interface.
  */
 export interface IWorker extends EventEmitter {
+  /**
+   * Cluster worker disconnect.
+   */
+  readonly disconnect?: () => void
   /**
    * Cluster worker id.
    */
   readonly id?: number
   /**
-   * Worker thread worker id.
+   * Cluster worker kill.
    */
-  readonly threadId?: number
+  readonly kill?: (signal?: string) => void
   /**
    * Registers an event handler.
    * @param event - The event.
@@ -249,25 +253,21 @@ export interface IWorker extends EventEmitter {
    * @param handler - The event handler.
    */
   readonly once: (event: string, handler: EventHandler<this>) => this
-  /**
-   * Calling `unref()` on a worker allows the thread to exit if this is the only
-   * active handle in the event system. If the worker is already `unref()`ed calling`unref()` again has no effect.
-   * @since v10.5.0
-   */
-  readonly unref?: () => void
   /**
    * Stop all JavaScript execution in the worker thread as soon as possible.
    * Returns a Promise for the exit code that is fulfilled when the `'exit' event` is emitted.
    */
   readonly terminate?: () => Promise<number>
   /**
-   * Cluster worker disconnect.
+   * Worker thread worker id.
    */
-  readonly disconnect?: () => void
+  readonly threadId?: number
   /**
-   * Cluster worker kill.
+   * Calling `unref()` on a worker allows the thread to exit if this is the only
+   * active handle in the event system. If the worker is already `unref()`ed calling`unref()` again has no effect.
+   * @since v10.5.0
    */
-  readonly kill?: (signal?: string) => void
+  readonly unref?: () => void
 }
 
 /**
@@ -275,11 +275,11 @@ export interface IWorker extends EventEmitter {
  * @internal
  */
 export interface WorkerNodeOptions {
-  workerOptions?: WorkerOptions
   env?: Record<string, unknown>
   tasksQueueBackPressureSize: number | undefined
   tasksQueueBucketSize: number | undefined
   tasksQueuePriority: boolean | undefined
+  workerOptions?: WorkerOptions
 }
 
 /**
@@ -291,47 +291,20 @@ export interface WorkerNodeOptions {
 export interface IWorkerNode<Worker extends IWorker, Data = unknown>
   extends EventEmitter {
   /**
-   * Worker.
-   */
-  readonly worker: Worker
-  /**
-   * Worker info.
-   */
-  readonly info: WorkerInfo
-  /**
-   * Worker usage statistics.
-   */
-  readonly usage: WorkerUsage
-  /**
-   * Worker choice strategy data.
-   * This is used to store data that are specific to the worker choice strategy.
-   */
-  strategyData?: StrategyData
-  /**
-   * Message channel (worker thread only).
-   */
-  readonly messageChannel?: MessageChannel
-  /**
-   * Tasks queue back pressure size.
-   * This is the number of tasks that can be enqueued before the worker node has back pressure.
-   */
-  tasksQueueBackPressureSize: number
-  /**
-   * Sets tasks queue priority.
-   * @param enablePriority - Whether to enable tasks queue priority.
+   * Clears tasks queue.
    */
-  readonly setTasksQueuePriority: (enablePriority: boolean) => void
+  readonly clearTasksQueue: () => void
   /**
-   * Tasks queue size.
-   * @returns The tasks queue size.
+   * Deletes task function worker usage statistics.
+   * @param name - The task function name.
+   * @returns `true` if the task function worker usage statistics were deleted, `false` otherwise.
    */
-  readonly tasksQueueSize: () => number
+  readonly deleteTaskFunctionWorkerUsage: (name: string) => boolean
   /**
-   * Enqueue task.
-   * @param task - The task to queue.
-   * @returns The tasks queue size.
+   * Dequeue last prioritized task.
+   * @returns The dequeued task.
    */
-  readonly enqueueTask: (task: Task<Data>) => number
+  readonly dequeueLastPrioritizedTask: () => Task<Data> | undefined
   /**
    * Dequeue task.
    * @param bucket - The prioritized bucket to dequeue from. @defaultValue 0
@@ -339,53 +312,80 @@ export interface IWorkerNode<Worker extends IWorker, Data = unknown>
    */
   readonly dequeueTask: (bucket?: number) => Task<Data> | undefined
   /**
-   * Dequeue last prioritized task.
-   * @returns The dequeued task.
+   * Enqueue task.
+   * @param task - The task to queue.
+   * @returns The tasks queue size.
    */
-  readonly dequeueLastPrioritizedTask: () => Task<Data> | undefined
+  readonly enqueueTask: (task: Task<Data>) => number
   /**
-   * Clears tasks queue.
+   * Gets task function worker usage statistics.
+   * @param name - The task function name.
+   * @returns The task function worker usage statistics if the task function worker usage statistics are initialized, `undefined` otherwise.
    */
-  readonly clearTasksQueue: () => void
+  readonly getTaskFunctionWorkerUsage: (name: string) => undefined | WorkerUsage
   /**
    * Whether the worker node has back pressure (i.e. its tasks queue is full).
    * @returns `true` if the worker node has back pressure, `false` otherwise.
    */
   readonly hasBackPressure: () => boolean
   /**
-   * Terminates the worker node.
+   * Worker info.
    */
-  readonly terminate: () => Promise<void>
+  readonly info: WorkerInfo
   /**
-   * Registers a worker event handler.
+   * Message channel (worker thread only).
+   */
+  readonly messageChannel?: MessageChannel
+  /**
+   * Registers once a worker event handler.
    * @param event - The event.
    * @param handler - The event handler.
    */
-  readonly registerWorkerEventHandler: (
+  readonly registerOnceWorkerEventHandler: (
     event: string,
     handler: EventHandler<Worker>
   ) => void
   /**
-   * Registers once a worker event handler.
+   * Registers a worker event handler.
    * @param event - The event.
    * @param handler - The event handler.
    */
-  readonly registerOnceWorkerEventHandler: (
+  readonly registerWorkerEventHandler: (
     event: string,
     handler: EventHandler<Worker>
   ) => void
   /**
-   * Gets task function worker usage statistics.
-   * @param name - The task function name.
-   * @returns The task function worker usage statistics if the task function worker usage statistics are initialized, `undefined` otherwise.
+   * Sets tasks queue priority.
+   * @param enablePriority - Whether to enable tasks queue priority.
    */
-  readonly getTaskFunctionWorkerUsage: (name: string) => WorkerUsage | undefined
+  readonly setTasksQueuePriority: (enablePriority: boolean) => void
   /**
-   * Deletes task function worker usage statistics.
-   * @param name - The task function name.
-   * @returns `true` if the task function worker usage statistics were deleted, `false` otherwise.
+   * Worker choice strategy data.
+   * This is used to store data that are specific to the worker choice strategy.
    */
-  readonly deleteTaskFunctionWorkerUsage: (name: string) => boolean
+  strategyData?: StrategyData
+  /**
+   * Tasks queue back pressure size.
+   * This is the number of tasks that can be enqueued before the worker node has back pressure.
+   */
+  tasksQueueBackPressureSize: number
+  /**
+   * Tasks queue size.
+   * @returns The tasks queue size.
+   */
+  readonly tasksQueueSize: () => number
+  /**
+   * Terminates the worker node.
+   */
+  readonly terminate: () => Promise<void>
+  /**
+   * Worker usage statistics.
+   */
+  readonly usage: WorkerUsage
+  /**
+   * Worker.
+   */
+  readonly worker: Worker
 }
 
 /**
index 0239153f2619c5473726d95aedcc85c88dfb416c..00d4dddab8aa9344af5de73580249b311ba17005 100644 (file)
@@ -14,9 +14,9 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
   /** @inheritdoc */
   public readonly capacity: number
   /** @inheritdoc */
-  public size!: number
-  /** @inheritdoc */
   public nodeArray: FixedQueueNode<T>[]
+  /** @inheritdoc */
+  public size!: number
 
   /**
    * Constructs a fixed queue.
@@ -30,29 +30,25 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
     this.clear()
   }
 
-  /** @inheritdoc */
-  public empty (): boolean {
-    return this.size === 0
-  }
-
-  /** @inheritdoc */
-  public full (): boolean {
-    return this.size === this.capacity
+  /**
+   * Checks the fixed queue size.
+   * @param size - Queue size.
+   */
+  private checkSize (size: number): void {
+    if (!Number.isSafeInteger(size)) {
+      throw new TypeError(
+        `Invalid fixed queue size: '${size.toString()}' is not an integer`
+      )
+    }
+    if (size < 0) {
+      throw new RangeError(`Invalid fixed queue size: ${size.toString()} < 0`)
+    }
   }
 
   /** @inheritdoc */
-  public abstract enqueue (data: T, priority?: number): number
-
-  /** @inheritdoc */
-  public get (index: number): T | undefined {
-    if (this.empty() || index >= this.size) {
-      return undefined
-    }
-    index += this.start
-    if (index >= this.capacity) {
-      index -= this.capacity
-    }
-    return this.nodeArray[index].data
+  public clear (): void {
+    this.start = 0
+    this.size = 0
   }
 
   /** @inheritdoc */
@@ -70,9 +66,28 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
   }
 
   /** @inheritdoc */
-  public clear (): void {
-    this.start = 0
-    this.size = 0
+  public empty (): boolean {
+    return this.size === 0
+  }
+
+  /** @inheritdoc */
+  public abstract enqueue (data: T, priority?: number): number
+
+  /** @inheritdoc */
+  public full (): boolean {
+    return this.size === this.capacity
+  }
+
+  /** @inheritdoc */
+  public get (index: number): T | undefined {
+    if (this.empty() || index >= this.size) {
+      return undefined
+    }
+    index += this.start
+    if (index >= this.capacity) {
+      index -= this.capacity
+    }
+    return this.nodeArray[index].data
   }
 
   /** @inheritdoc */
@@ -83,8 +98,8 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
       next: () => {
         if (i >= this.size) {
           return {
-            value: undefined,
             done: true,
+            value: undefined,
           }
         }
         const value = this.nodeArray[index].data
@@ -94,25 +109,10 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
           index = 0
         }
         return {
-          value,
           done: false,
+          value,
         }
       },
     }
   }
-
-  /**
-   * Checks the fixed queue size.
-   * @param size - Queue size.
-   */
-  private checkSize (size: number): void {
-    if (!Number.isSafeInteger(size)) {
-      throw new TypeError(
-        `Invalid fixed queue size: '${size.toString()}' is not an integer`
-      )
-    }
-    if (size < 0) {
-      throw new RangeError(`Invalid fixed queue size: ${size.toString()} < 0`)
-    }
-  }
 }
index 7a3976d76b39bc2dbb677d90235d8e5d862fab46..275a78403f9292c67c12e58cf39aaff29a076dde 100644 (file)
@@ -1,6 +1,7 @@
-import { AbstractFixedQueue } from './abstract-fixed-queue.js'
 import type { IFixedQueue } from './queue-types.js'
 
+import { AbstractFixedQueue } from './abstract-fixed-queue.js'
+
 /**
  * Fixed priority queue.
  * @typeParam T - Type of fixed priority queue data.
index 366e41f1540af021368d6505de33bcb61faca959..736c40fb1d872f53ac228b16579f1ffc839393cf 100644 (file)
@@ -1,6 +1,7 @@
-import { AbstractFixedQueue } from './abstract-fixed-queue.js'
 import type { IFixedQueue } from './queue-types.js'
 
+import { AbstractFixedQueue } from './abstract-fixed-queue.js'
+
 /**
  * Fixed queue.
  * @typeParam T - Type of fixed queue data.
index 6b1fd308a55ff320e3f14aefecaacb0320a7f9a8..c8cb84f02bd083feb856dce5a2c9de759f307fea 100644 (file)
@@ -15,10 +15,10 @@ import {
  * @internal
  */
 export class PriorityQueue<T> {
-  private head!: PriorityQueueNode<T>
-  private tail!: PriorityQueueNode<T>
   private readonly bucketSize: number
+  private head!: PriorityQueueNode<T>
   private priorityEnabled: boolean
+  private tail!: PriorityQueueNode<T>
   /** The priority queue maximum size. */
   public maxSize!: number
 
@@ -45,87 +45,27 @@ export class PriorityQueue<T> {
     this.clear()
   }
 
-  /**
-   * The priority queue size.
-   * @returns The priority queue size.
-   */
-  public get size (): number {
-    let node: PriorityQueueNode<T> | undefined = this.tail
-    let size = 0
-    while (node != null) {
-      size += node.size
-      node = node.next
-    }
-    return size
-  }
-
-  /**
-   * Whether priority is enabled.
-   * @returns Whether priority is enabled.
-   */
-  public get enablePriority (): boolean {
-    return this.priorityEnabled
-  }
-
-  /**
-   * Enables/disables priority.
-   * @param enablePriority - Whether to enable priority.
-   */
-  public set enablePriority (enablePriority: boolean) {
-    if (this.priorityEnabled === enablePriority) {
-      return
+  private getPriorityQueueNode (
+    nodeArray?: FixedQueueNode<T>[]
+  ): PriorityQueueNode<T> {
+    let fixedQueue: IFixedQueue<T>
+    if (this.priorityEnabled) {
+      fixedQueue = new FixedPriorityQueue(this.bucketSize)
+    } else {
+      fixedQueue = new FixedQueue(this.bucketSize)
     }
-    this.priorityEnabled = enablePriority
-    let head: PriorityQueueNode<T>
-    let tail: PriorityQueueNode<T>
-    let prev: PriorityQueueNode<T> | undefined
-    let node: PriorityQueueNode<T> | undefined = this.tail
-    let buckets = 0
-    while (node != null) {
-      const currentNode = this.getPriorityQueueNode(node.nodeArray)
-      if (buckets === 0) {
-        tail = currentNode
-      }
-      if (prev != null) {
-        prev.next = currentNode
-      }
-      prev = currentNode
-      if (node.next == null) {
-        head = currentNode
-      }
-      ++buckets
-      node = node.next
+    if (nodeArray != null) {
+      fixedQueue.nodeArray = nodeArray
     }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.head = head!
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    this.tail = tail!
-  }
-
-  /**
-   * The number of filled prioritized buckets.
-   * @returns The number of filled prioritized buckets.
-   */
-  public get buckets (): number {
-    return Math.trunc(this.size / this.bucketSize)
+    return fixedQueue
   }
 
   /**
-   * 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.
+   * Clears the priority queue.
    */
-  public enqueue (data: T, priority?: number): number {
-    if (this.head.full()) {
-      this.head = this.head.next = this.getPriorityQueueNode()
-    }
-    this.head.enqueue(data, priority)
-    const size = this.size
-    if (size > this.maxSize) {
-      this.maxSize = size
-    }
-    return size
+  public clear (): void {
+    this.head = this.tail = this.getPriorityQueueNode()
+    this.maxSize = 0
   }
 
   /**
@@ -181,11 +121,21 @@ export class PriorityQueue<T> {
   }
 
   /**
-   * Clears the priority queue.
+   * 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 clear (): void {
-    this.head = this.tail = this.getPriorityQueueNode()
-    this.maxSize = 0
+  public enqueue (data: T, priority?: number): number {
+    if (this.head.full()) {
+      this.head = this.head.next = this.getPriorityQueueNode()
+    }
+    this.head.enqueue(data, priority)
+    const size = this.size
+    if (size > this.maxSize) {
+      this.maxSize = size
+    }
+    return size
   }
 
   /**
@@ -201,8 +151,8 @@ export class PriorityQueue<T> {
         const value = node.get(index) as T
         if (value == null) {
           return {
-            value: undefined,
             done: true,
+            value: undefined,
           }
         }
         ++index
@@ -211,25 +161,75 @@ export class PriorityQueue<T> {
           index = 0
         }
         return {
-          value,
           done: false,
+          value,
         }
       },
     }
   }
 
-  private getPriorityQueueNode (
-    nodeArray?: FixedQueueNode<T>[]
-  ): PriorityQueueNode<T> {
-    let fixedQueue: IFixedQueue<T>
-    if (this.priorityEnabled) {
-      fixedQueue = new FixedPriorityQueue(this.bucketSize)
-    } else {
-      fixedQueue = new FixedQueue(this.bucketSize)
+  /**
+   * The number of filled prioritized buckets.
+   * @returns The number of filled prioritized buckets.
+   */
+  public get buckets (): number {
+    return Math.trunc(this.size / this.bucketSize)
+  }
+
+  /**
+   * Whether priority is enabled.
+   * @returns Whether priority is enabled.
+   */
+  public get enablePriority (): boolean {
+    return this.priorityEnabled
+  }
+
+  /**
+   * Enables/disables priority.
+   * @param enablePriority - Whether to enable priority.
+   */
+  public set enablePriority (enablePriority: boolean) {
+    if (this.priorityEnabled === enablePriority) {
+      return
     }
-    if (nodeArray != null) {
-      fixedQueue.nodeArray = nodeArray
+    this.priorityEnabled = enablePriority
+    let head: PriorityQueueNode<T>
+    let tail: PriorityQueueNode<T>
+    let prev: PriorityQueueNode<T> | undefined
+    let node: PriorityQueueNode<T> | undefined = this.tail
+    let buckets = 0
+    while (node != null) {
+      const currentNode = this.getPriorityQueueNode(node.nodeArray)
+      if (buckets === 0) {
+        tail = currentNode
+      }
+      if (prev != null) {
+        prev.next = currentNode
+      }
+      prev = currentNode
+      if (node.next == null) {
+        head = currentNode
+      }
+      ++buckets
+      node = node.next
     }
-    return fixedQueue
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.head = head!
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    this.tail = tail!
+  }
+
+  /**
+   * The priority queue size.
+   * @returns The priority queue size.
+   */
+  public get size (): number {
+    let node: PriorityQueueNode<T> | undefined = this.tail
+    let size = 0
+    while (node != null) {
+      size += node.size
+      node = node.next
+    }
+    return size
   }
 }
index 83d7f7f2963685bb89fa2b4ab198a322f339131e..1e374b594a91052f440d51d47686655cea83abda 100644 (file)
@@ -20,22 +20,28 @@ export interface FixedQueueNode<T> {
  * @internal
  */
 export interface IFixedQueue<T> {
+  /**
+   * Returns an iterator for the fixed queue.
+   * @returns An iterator for the fixed queue.
+   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
+   */
+  [Symbol.iterator]: () => Iterator<T>
   /** The fixed queue capacity. */
   readonly capacity: number
-  /** The fixed queue size. */
-  readonly size: number
-  /** The fixed queue node array. */
-  nodeArray: FixedQueueNode<T>[]
+  /**
+   * Clears the fixed queue.
+   */
+  clear: () => void
+  /**
+   * Dequeue data from the fixed queue.
+   * @returns The dequeued data or `undefined` if the fixed queue is empty.
+   */
+  dequeue: () => T | undefined
   /**
    * Checks if the fixed queue is empty.
    * @returns `true` if the fixed queue is empty, `false` otherwise.
    */
   empty: () => boolean
-  /**
-   * Checks if the fixed queue is full.
-   * @returns `true` if the fixed queue is full, `false` otherwise.
-   */
-  full: () => boolean
   /**
    * Enqueue data into the fixed queue.
    * @param data - Data to enqueue.
@@ -44,27 +50,21 @@ export interface IFixedQueue<T> {
    * @throws If the fixed queue is full.
    */
   enqueue: (data: T, priority?: number) => number
+  /**
+   * Checks if the fixed queue is full.
+   * @returns `true` if the fixed queue is full, `false` otherwise.
+   */
+  full: () => boolean
   /**
    * Gets data from the fixed queue.
    * @param index - The index of the data to get.
    * @returns The data at the index or `undefined` if the fixed queue is empty or the index is out of bounds.
    */
   get: (index: number) => T | undefined
-  /**
-   * Dequeue data from the fixed queue.
-   * @returns The dequeued data or `undefined` if the fixed queue is empty.
-   */
-  dequeue: () => T | undefined
-  /**
-   * Clears the fixed queue.
-   */
-  clear: () => void
-  /**
-   * Returns an iterator for the fixed queue.
-   * @returns An iterator for the fixed queue.
-   * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
-   */
-  [Symbol.iterator]: () => Iterator<T>
+  /** The fixed queue node array. */
+  nodeArray: FixedQueueNode<T>[]
+  /** The fixed queue size. */
+  readonly size: number
 }
 
 /**
index b29f801298b065f542f3ee981eb7cf3103456e42..8ccb1a744db449172c38978dfd3e7d799974d760 100644 (file)
@@ -11,17 +11,17 @@ import type { KillBehavior } from './worker/worker-options.js'
  */
 export interface WorkerError<Data = unknown> {
   /**
-   * Task function name triggering the error.
+   * Data triggering the error.
    */
-  readonly name: string
+  readonly data?: Data
   /**
    * Error message.
    */
   readonly message: string
   /**
-   * Data triggering the error.
+   * Task function name triggering the error.
    */
-  readonly data?: Data
+  readonly name: string
 }
 
 /**
@@ -30,21 +30,21 @@ export interface WorkerError<Data = unknown> {
  */
 export interface TaskPerformance {
   /**
-   * Task name.
+   * Task event loop utilization.
    */
-  readonly name: string
+  readonly elu?: EventLoopUtilization
   /**
-   * Task performance timestamp.
+   * Task name.
    */
-  readonly timestamp: number
+  readonly name: string
   /**
    * Task runtime.
    */
   readonly runTime?: number
   /**
-   * Task event loop utilization.
+   * Task performance timestamp.
    */
-  readonly elu?: EventLoopUtilization
+  readonly timestamp: number
 }
 
 /**
@@ -52,14 +52,14 @@ export interface TaskPerformance {
  * @internal
  */
 export interface WorkerStatistics {
-  /**
-   * Whether the worker computes the task runtime or not.
-   */
-  readonly runTime: boolean
   /**
    * Whether the worker computes the task event loop utilization (ELU) or not.
    */
   readonly elu: boolean
+  /**
+   * Whether the worker computes the task runtime or not.
+   */
+  readonly runTime: boolean
 }
 
 /**
@@ -86,14 +86,14 @@ export interface TaskFunctionProperties {
  * @internal
  */
 export interface Task<Data = unknown> {
-  /**
-   * Task name.
-   */
-  readonly name?: string
   /**
    * Task input data that will be passed to the worker.
    */
   readonly data?: Data
+  /**
+   * Task name.
+   */
+  readonly name?: string
   /**
    * Task priority. Lower values have higher priority.
    * @defaultValue 0
@@ -104,17 +104,17 @@ export interface Task<Data = unknown> {
    */
   readonly strategy?: WorkerChoiceStrategy
   /**
-   * Array of transferable objects.
+   * Task UUID.
    */
-  readonly transferList?: readonly TransferListItem[]
+  readonly taskId?: `${string}-${string}-${string}-${string}-${string}`
   /**
    * Timestamp.
    */
   readonly timestamp?: number
   /**
-   * Task UUID.
+   * Array of transferable objects.
    */
-  readonly taskId?: `${string}-${string}-${string}-${string}-${string}`
+  readonly transferList?: readonly TransferListItem[]
 }
 
 /**
@@ -126,28 +126,36 @@ export interface Task<Data = unknown> {
 export interface MessageValue<Data = unknown, ErrorData = unknown>
   extends Task<Data> {
   /**
-   * Worker id.
+   * Whether the worker starts or stops its activity check.
    */
-  readonly workerId?: number
+  readonly checkActive?: boolean
   /**
    * Kill code.
    */
-  readonly kill?: KillBehavior | true | 'success' | 'failure'
+  readonly kill?: 'failure' | 'success' | KillBehavior | true
   /**
-   * Worker error.
+   * Message port.
    */
-  readonly workerError?: WorkerError<ErrorData>
+  readonly port?: MessagePort
   /**
-   * Task performance.
+   * Whether the worker is ready or not.
    */
-  readonly taskPerformance?: TaskPerformance
+  readonly ready?: boolean
+  /**
+   * Whether the worker computes the given statistics or not.
+   */
+  readonly statistics?: WorkerStatistics
+  /**
+   * Task function serialized to string.
+   */
+  readonly taskFunction?: string
   /**
    * Task function operation:
    * - `'add'` - Add a task function.
    * - `'remove'` - Remove a task function.
    * - `'default'` - Set a task function as default.
    */
-  readonly taskFunctionOperation?: 'add' | 'remove' | 'default'
+  readonly taskFunctionOperation?: 'add' | 'default' | 'remove'
   /**
    * Whether the task function operation is successful or not.
    */
@@ -156,30 +164,22 @@ export interface MessageValue<Data = unknown, ErrorData = unknown>
    * Task function properties.
    */
   readonly taskFunctionProperties?: TaskFunctionProperties
-  /**
-   * Task function serialized to string.
-   */
-  readonly taskFunction?: string
   /**
    * Task functions properties.
    */
   readonly taskFunctionsProperties?: TaskFunctionProperties[]
   /**
-   * Whether the worker computes the given statistics or not.
-   */
-  readonly statistics?: WorkerStatistics
-  /**
-   * Whether the worker is ready or not.
+   * Task performance.
    */
-  readonly ready?: boolean
+  readonly taskPerformance?: TaskPerformance
   /**
-   * Whether the worker starts or stops its activity check.
+   * Worker error.
    */
-  readonly checkActive?: boolean
+  readonly workerError?: WorkerError<ErrorData>
   /**
-   * Message port.
+   * Worker id.
    */
-  readonly port?: MessagePort
+  readonly workerId?: number
 }
 
 /**
@@ -189,21 +189,21 @@ export interface MessageValue<Data = unknown, ErrorData = unknown>
  */
 export interface PromiseResponseWrapper<Response = unknown> {
   /**
-   * Resolve callback to fulfill the promise.
+   * The asynchronous resource used to track the task execution.
    */
-  readonly resolve: (value: Response | PromiseLike<Response>) => void
+  readonly asyncResource?: AsyncResource
   /**
    * Reject callback to reject the promise.
    */
   readonly reject: (reason?: unknown) => void
   /**
-   * The worker node key executing the task.
+   * Resolve callback to fulfill the promise.
    */
-  readonly workerNodeKey: number
+  readonly resolve: (value: PromiseLike<Response> | Response) => void
   /**
-   * The asynchronous resource used to track the task execution.
+   * The worker node key executing the task.
    */
-  readonly asyncResource?: AsyncResource
+  readonly workerNodeKey: number
 }
 
 /**
index ebbefc75ba4cc8c4e09bcc3073e372085466c181..3b5f11fcfb259b829ead8cdfefa4535d68dae4d8 100644 (file)
@@ -1,7 +1,8 @@
 import type { Worker } from 'node:cluster'
-import { performance } from 'node:perf_hooks'
 import type { MessagePort } from 'node:worker_threads'
 
+import { performance } from 'node:perf_hooks'
+
 import type {
   MessageValue,
   Task,
@@ -9,13 +10,6 @@ import type {
   TaskPerformance,
   WorkerStatistics,
 } from '../utility-types.js'
-import {
-  buildTaskFunctionProperties,
-  DEFAULT_TASK_NAME,
-  EMPTY_FUNCTION,
-  isAsyncFunction,
-  isPlainObject,
-} from '../utils.js'
 import type {
   TaskAsyncFunction,
   TaskFunction,
@@ -24,6 +18,14 @@ import type {
   TaskFunctions,
   TaskSyncFunction,
 } from './task-functions.js'
+
+import {
+  buildTaskFunctionProperties,
+  DEFAULT_TASK_NAME,
+  EMPTY_FUNCTION,
+  isAsyncFunction,
+  isPlainObject,
+} from '../utils.js'
 import {
   checkTaskFunctionName,
   checkValidTaskFunctionObjectEntry,
@@ -37,15 +39,15 @@ const DEFAULT_WORKER_OPTIONS: WorkerOptions = {
    * The kill behavior option on this worker or its default value.
    */
   killBehavior: KillBehaviors.SOFT,
+  /**
+   * The function to call when the worker is killed.
+   */
+  killHandler: EMPTY_FUNCTION,
   /**
    * The maximum time to keep this worker active while idle.
    * The pool automatically checks and terminates this worker when the time expires.
    */
   maxInactiveTime: DEFAULT_MAX_INACTIVE_TIME,
-  /**
-   * The function to call when the worker is killed.
-   */
-  killHandler: EMPTY_FUNCTION,
 }
 
 /**
@@ -55,30 +57,131 @@ const DEFAULT_WORKER_OPTIONS: WorkerOptions = {
  * @typeParam Response - Type of response the worker sends back to the main worker. This can only be structured-cloneable data.
  */
 export abstract class AbstractWorker<
-  MainWorker extends Worker | MessagePort,
+  MainWorker extends MessagePort | Worker,
   Data = unknown,
   Response = unknown
 > {
   /**
-   * Worker id.
+   * Handler id of the `activeInterval` worker activity check.
    */
-  protected abstract id: number
+  protected activeInterval?: NodeJS.Timeout
   /**
-   * Task function object(s) processed by the worker when the pool's `execution` function is invoked.
+   * Worker id.
    */
-  protected taskFunctions!: Map<string, TaskFunctionObject<Data, Response>>
+  protected abstract id: number
   /**
    * Timestamp of the last task processed by this worker.
    */
   protected lastTaskTimestamp!: number
+  /**
+   * Runs the given task.
+   * @param task - The task to execute.
+   */
+  protected readonly run = (task: Task<Data>): void => {
+    const { data, name, taskId } = task
+    const taskFunctionName = name ?? DEFAULT_TASK_NAME
+    if (!this.taskFunctions.has(taskFunctionName)) {
+      this.sendToMainWorker({
+        taskId,
+        workerError: {
+          data,
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          message: `Task function '${name!}' not found`,
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          name: name!,
+        },
+      })
+      return
+    }
+    const fn = this.taskFunctions.get(taskFunctionName)?.taskFunction
+    if (isAsyncFunction(fn)) {
+      this.runAsync(fn as TaskAsyncFunction<Data, Response>, task)
+    } else {
+      this.runSync(fn as TaskSyncFunction<Data, Response>, task)
+    }
+  }
+
+  /**
+   * Runs the given task function asynchronously.
+   * @param fn - Task function that will be executed.
+   * @param task - Input data for the task function.
+   */
+  protected readonly runAsync = (
+    fn: TaskAsyncFunction<Data, Response>,
+    task: Task<Data>
+  ): void => {
+    const { data, name, taskId } = task
+    let taskPerformance = this.beginTaskPerformance(name)
+    fn(data)
+      .then(res => {
+        taskPerformance = this.endTaskPerformance(taskPerformance)
+        this.sendToMainWorker({
+          data: res,
+          taskId,
+          taskPerformance,
+        })
+        return undefined
+      })
+      .catch((error: unknown) => {
+        this.sendToMainWorker({
+          taskId,
+          workerError: {
+            data,
+            message: this.handleError(error as Error | string),
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            name: name!,
+          },
+        })
+      })
+      .finally(() => {
+        this.updateLastTaskTimestamp()
+      })
+      .catch(EMPTY_FUNCTION)
+  }
+
+  /**
+   * Runs the given task function synchronously.
+   * @param fn - Task function that will be executed.
+   * @param task - Input data for the task function.
+   */
+  protected readonly runSync = (
+    fn: TaskSyncFunction<Data, Response>,
+    task: Task<Data>
+  ): void => {
+    const { data, name, taskId } = task
+    try {
+      let taskPerformance = this.beginTaskPerformance(name)
+      const res = fn(data)
+      taskPerformance = this.endTaskPerformance(taskPerformance)
+      this.sendToMainWorker({
+        data: res,
+        taskId,
+        taskPerformance,
+      })
+    } catch (error) {
+      this.sendToMainWorker({
+        taskId,
+        workerError: {
+          data,
+          message: this.handleError(error as Error | string),
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          name: name!,
+        },
+      })
+    } finally {
+      this.updateLastTaskTimestamp()
+    }
+  }
+
   /**
    * Performance statistics computation requirements.
    */
   protected statistics?: WorkerStatistics
+
   /**
-   * Handler id of the `activeInterval` worker activity check.
+   * Task function object(s) processed by the worker when the pool's `execution` function is invoked.
    */
-  protected activeInterval?: NodeJS.Timeout
+  protected taskFunctions!: Map<string, TaskFunctionObject<Data, Response>>
 
   /**
    * Constructs a new poolifier worker.
@@ -89,7 +192,7 @@ export abstract class AbstractWorker<
    */
   public constructor (
     protected readonly isMain: boolean | undefined,
-    private readonly mainWorker: MainWorker | undefined | null,
+    private readonly mainWorker: MainWorker | null | undefined,
     taskFunctions: TaskFunction<Data, Response> | TaskFunctions<Data, Response>,
     protected opts: WorkerOptions = DEFAULT_WORKER_OPTIONS
   ) {
@@ -104,246 +207,62 @@ export abstract class AbstractWorker<
     }
   }
 
-  private checkWorkerOptions (opts: WorkerOptions): void {
-    checkValidWorkerOptions(opts)
-    this.opts = { ...DEFAULT_WORKER_OPTIONS, ...opts }
+  /**
+   * Returns the main worker.
+   * @returns Reference to the main worker.
+   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the main worker is not set.
+   */
+  protected getMainWorker (): MainWorker {
+    if (this.mainWorker == null) {
+      throw new Error('Main worker not set')
+    }
+    return this.mainWorker
   }
 
   /**
-   * Checks if the `taskFunctions` parameter is passed to the constructor and valid.
-   * @param taskFunctions - The task function(s) parameter that should be checked.
+   * Handles an error and convert it to a string so it can be sent back to the main worker.
+   * @param error - The error raised by the worker.
+   * @returns The error message.
    */
-  private checkTaskFunctions (
-    taskFunctions:
-      | TaskFunction<Data, Response>
-      | TaskFunctions<Data, Response>
-      | undefined
-  ): void {
-    if (taskFunctions == null) {
-      throw new Error('taskFunctions parameter is mandatory')
-    }
-    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
-    if (typeof taskFunctions === 'function') {
-      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',
-        fnObj
-      )
-    } else if (isPlainObject(taskFunctions)) {
-      let firstEntry = true
-      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, fnObj)
-          firstEntry = false
-        }
-        this.taskFunctions.set(name, fnObj)
-      }
-      if (firstEntry) {
-        throw new Error('taskFunctions parameter object is empty')
-      }
-    } else {
-      throw new TypeError(
-        'taskFunctions parameter is not a function or a plain object'
-      )
-    }
+  protected handleError (error: Error | string): string {
+    return error instanceof Error ? error.message : error
   }
 
   /**
-   * Checks if the worker has a task function with the given name.
-   * @param name - The name of the task function to check.
-   * @returns Whether the worker has a task function with the given name or not.
+   * Handles a kill message sent by the main worker.
+   * @param message - The kill message.
    */
-  public hasTaskFunction (name: string): TaskFunctionOperationResult {
-    try {
-      checkTaskFunctionName(name)
-    } catch (error) {
-      return { status: false, error: error as Error }
+  protected handleKillMessage (message: MessageValue<Data>): void {
+    this.stopCheckActive()
+    if (isAsyncFunction(this.opts.killHandler)) {
+      ;(this.opts.killHandler as () => Promise<void>)()
+        .then(() => {
+          this.sendToMainWorker({ kill: 'success' })
+          return undefined
+        })
+        .catch(() => {
+          this.sendToMainWorker({ kill: 'failure' })
+        })
+    } else {
+      try {
+        ;(this.opts.killHandler as (() => void) | undefined)?.()
+        this.sendToMainWorker({ kill: 'success' })
+      } catch {
+        this.sendToMainWorker({ kill: 'failure' })
+      }
     }
-    return { status: this.taskFunctions.has(name) }
   }
 
   /**
-   * Adds a task function to the worker.
-   * If a task function with the same name already exists, it is replaced.
-   * @param name - The name of the task function to add.
-   * @param fn - The task function to add.
-   * @returns Whether the task function was added or not.
-   */
-  public addTaskFunction (
-    name: string,
-    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
-  ): TaskFunctionOperationResult {
-    try {
-      checkTaskFunctionName(name)
-      if (name === DEFAULT_TASK_NAME) {
-        throw new Error(
-          'Cannot add a task function with the default reserved name'
-        )
-      }
-      if (typeof fn === 'function') {
-        fn = { taskFunction: fn } satisfies TaskFunctionObject<Data, Response>
-      }
-      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, fn)
-      }
-      this.taskFunctions.set(name, fn)
-      this.sendTaskFunctionsPropertiesToMainWorker()
-      return { status: true }
-    } catch (error) {
-      return { status: false, error: error as Error }
-    }
-  }
-
-  /**
-   * Removes a task function from the worker.
-   * @param name - The name of the task function to remove.
-   * @returns Whether the task function existed and was removed or not.
-   */
-  public removeTaskFunction (name: string): TaskFunctionOperationResult {
-    try {
-      checkTaskFunctionName(name)
-      if (name === DEFAULT_TASK_NAME) {
-        throw new Error(
-          'Cannot remove the task function with the default reserved name'
-        )
-      }
-      if (
-        this.taskFunctions.get(name) ===
-        this.taskFunctions.get(DEFAULT_TASK_NAME)
-      ) {
-        throw new Error(
-          'Cannot remove the task function used as the default task function'
-        )
-      }
-      const deleteStatus = this.taskFunctions.delete(name)
-      this.sendTaskFunctionsPropertiesToMainWorker()
-      return { status: deleteStatus }
-    } catch (error) {
-      return { status: false, error: error as Error }
-    }
-  }
-
-  /**
-   * Lists the properties of the worker's task functions.
-   * @returns The properties of the worker's task functions.
-   */
-  public listTaskFunctionsProperties (): TaskFunctionProperties[] {
-    let defaultTaskFunctionName = DEFAULT_TASK_NAME
-    for (const [name, fnObj] of this.taskFunctions) {
-      if (
-        name !== 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 [
-      buildTaskFunctionProperties(
-        DEFAULT_TASK_NAME,
-        this.taskFunctions.get(DEFAULT_TASK_NAME)
-      ),
-      buildTaskFunctionProperties(
-        defaultTaskFunctionName,
-        this.taskFunctions.get(defaultTaskFunctionName)
-      ),
-      ...taskFunctionsProperties,
-    ]
-  }
-
-  /**
-   * Sets the default task function to use in the worker.
-   * @param name - The name of the task function to use as default task function.
-   * @returns Whether the default task function was set or not.
-   */
-  public setDefaultTaskFunction (name: string): TaskFunctionOperationResult {
-    try {
-      checkTaskFunctionName(name)
-      if (name === DEFAULT_TASK_NAME) {
-        throw new Error(
-          'Cannot set the default task function reserved name as the default task function'
-        )
-      }
-      if (!this.taskFunctions.has(name)) {
-        throw new Error(
-          'Cannot set the default task function to a non-existing task function'
-        )
-      }
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      this.taskFunctions.set(DEFAULT_TASK_NAME, this.taskFunctions.get(name)!)
-      this.sendTaskFunctionsPropertiesToMainWorker()
-      return { status: true }
-    } catch (error) {
-      return { status: false, error: error as Error }
-    }
-  }
-
-  /**
-   * Handles the ready message sent by the main worker.
-   * @param message - The ready message.
+   * Handles the ready message sent by the main worker.
+   * @param message - The ready message.
    */
   protected abstract handleReadyMessage (message: MessageValue<Data>): void
 
-  /**
-   * Worker message listener.
-   * @param message - The received message.
-   */
-  protected messageListener (message: MessageValue<Data>): void {
-    this.checkMessageWorkerId(message)
-    const {
-      statistics,
-      checkActive,
-      taskFunctionOperation,
-      taskId,
-      data,
-      kill,
-    } = message
-    if (statistics != null) {
-      // Statistics message received
-      this.statistics = statistics
-    } else if (checkActive != null) {
-      // Check active message received
-      checkActive ? this.startCheckActive() : this.stopCheckActive()
-    } else if (taskFunctionOperation != null) {
-      // Task function operation message received
-      this.handleTaskFunctionOperationMessage(message)
-    } else if (taskId != null && data != null) {
-      // Task message received
-      this.run(message)
-    } else if (kill === true) {
-      // Kill message received
-      this.handleKillMessage(message)
-    }
-  }
-
   protected handleTaskFunctionOperationMessage (
     message: MessageValue<Data>
   ): void {
-    const { taskFunctionOperation, taskFunctionProperties, taskFunction } =
+    const { taskFunction, taskFunctionOperation, taskFunctionProperties } =
       message
     if (taskFunctionProperties == null) {
       throw new Error(
@@ -373,11 +292,12 @@ export abstract class AbstractWorker<
       case 'default':
         response = this.setDefaultTaskFunction(taskFunctionProperties.name)
         break
+      // eslint-disable-next-line perfectionist/sort-switch-case
       default:
-        response = { status: false, error: new Error('Unknown task operation') }
+        response = { error: new Error('Unknown task operation'), status: false }
         break
     }
-    const { status, error } = response
+    const { error, status } = response
     this.sendToMainWorker({
       taskFunctionOperation,
       taskFunctionOperationStatus: status,
@@ -385,71 +305,72 @@ export abstract class AbstractWorker<
       ...(!status &&
         error != null && {
         workerError: {
-          name: taskFunctionProperties.name,
           message: this.handleError(error as Error | string),
+          name: taskFunctionProperties.name,
         },
       }),
     })
   }
 
   /**
-   * Handles a kill message sent by the main worker.
-   * @param message - The kill message.
+   * Worker message listener.
+   * @param message - The received message.
    */
-  protected handleKillMessage (message: MessageValue<Data>): void {
-    this.stopCheckActive()
-    if (isAsyncFunction(this.opts.killHandler)) {
-      ;(this.opts.killHandler as () => Promise<void>)()
-        .then(() => {
-          this.sendToMainWorker({ kill: 'success' })
-          return undefined
-        })
-        .catch(() => {
-          this.sendToMainWorker({ kill: 'failure' })
-        })
-    } else {
-      try {
-        ;(this.opts.killHandler as (() => void) | undefined)?.()
-        this.sendToMainWorker({ kill: 'success' })
-      } catch {
-        this.sendToMainWorker({ kill: 'failure' })
-      }
+  protected messageListener (message: MessageValue<Data>): void {
+    this.checkMessageWorkerId(message)
+    const {
+      checkActive,
+      data,
+      kill,
+      statistics,
+      taskFunctionOperation,
+      taskId,
+    } = message
+    if (statistics != null) {
+      // Statistics message received
+      this.statistics = statistics
+    } else if (checkActive != null) {
+      // Check active message received
+      checkActive ? this.startCheckActive() : this.stopCheckActive()
+    } else if (taskFunctionOperation != null) {
+      // Task function operation message received
+      this.handleTaskFunctionOperationMessage(message)
+    } else if (taskId != null && data != null) {
+      // Task message received
+      this.run(message)
+    } else if (kill === true) {
+      // Kill message received
+      this.handleKillMessage(message)
     }
   }
 
   /**
-   * Check if the message worker id is set and matches the worker id.
-   * @param message - The message to check.
-   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the message worker id is not set or does not match the worker id.
+   * Sends task functions properties to the main worker.
    */
-  private checkMessageWorkerId (message: MessageValue<Data>): void {
-    if (message.workerId == null) {
-      throw new Error('Message worker id is not set')
-    } else if (message.workerId !== this.id) {
-      throw new Error(
-        `Message worker id ${message.workerId.toString()} does not match the worker id ${this.id.toString()}`
-      )
-    }
+  protected sendTaskFunctionsPropertiesToMainWorker (): void {
+    this.sendToMainWorker({
+      taskFunctionsProperties: this.listTaskFunctionsProperties(),
+    })
   }
 
   /**
-   * Starts the worker check active interval.
+   * Sends a message to main worker.
+   * @param message - The response message.
    */
-  private startCheckActive (): void {
-    this.lastTaskTimestamp = performance.now()
-    this.activeInterval = setInterval(
-      this.checkActive.bind(this),
-      (this.opts.maxInactiveTime ?? DEFAULT_MAX_INACTIVE_TIME) / 2
-    )
-  }
+  protected abstract sendToMainWorker (
+    message: MessageValue<Response, Data>
+  ): void
 
-  /**
-   * Stops the worker check active interval.
-   */
-  private stopCheckActive (): void {
-    if (this.activeInterval != null) {
-      clearInterval(this.activeInterval)
-      delete this.activeInterval
+  private beginTaskPerformance (name?: string): TaskPerformance {
+    if (this.statistics == null) {
+      throw new Error('Performance statistics computation requirements not set')
+    }
+    return {
+      name: name ?? DEFAULT_TASK_NAME,
+      timestamp: performance.now(),
+      ...(this.statistics.elu && {
+        elu: performance.eventLoopUtilization(),
+      }),
     }
   }
 
@@ -466,176 +387,259 @@ export abstract class AbstractWorker<
   }
 
   /**
-   * Returns the main worker.
-   * @returns Reference to the main worker.
-   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the main worker is not set.
+   * Check if the message worker id is set and matches the worker id.
+   * @param message - The message to check.
+   * @throws {@link https://nodejs.org/api/errors.html#class-error} If the message worker id is not set or does not match the worker id.
    */
-  protected getMainWorker (): MainWorker {
-    if (this.mainWorker == null) {
-      throw new Error('Main worker not set')
+  private checkMessageWorkerId (message: MessageValue<Data>): void {
+    if (message.workerId == null) {
+      throw new Error('Message worker id is not set')
+    } else if (message.workerId !== this.id) {
+      throw new Error(
+        `Message worker id ${message.workerId.toString()} does not match the worker id ${this.id.toString()}`
+      )
     }
-    return this.mainWorker
   }
 
   /**
-   * Sends a message to main worker.
-   * @param message - The response message.
+   * Checks if the `taskFunctions` parameter is passed to the constructor and valid.
+   * @param taskFunctions - The task function(s) parameter that should be checked.
    */
-  protected abstract sendToMainWorker (
-    message: MessageValue<Response, Data>
-  ): void
-
-  /**
-   * Sends task functions properties to the main worker.
-   */
-  protected sendTaskFunctionsPropertiesToMainWorker (): void {
-    this.sendToMainWorker({
-      taskFunctionsProperties: this.listTaskFunctionsProperties(),
-    })
+  private checkTaskFunctions (
+    taskFunctions:
+      | TaskFunction<Data, Response>
+      | TaskFunctions<Data, Response>
+      | undefined
+  ): void {
+    if (taskFunctions == null) {
+      throw new Error('taskFunctions parameter is mandatory')
+    }
+    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
+    if (typeof taskFunctions === 'function') {
+      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',
+        fnObj
+      )
+    } else if (isPlainObject(taskFunctions)) {
+      let firstEntry = true
+      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, fnObj)
+          firstEntry = false
+        }
+        this.taskFunctions.set(name, fnObj)
+      }
+      if (firstEntry) {
+        throw new Error('taskFunctions parameter object is empty')
+      }
+    } else {
+      throw new TypeError(
+        'taskFunctions parameter is not a function or a plain object'
+      )
+    }
+  }
+
+  private checkWorkerOptions (opts: WorkerOptions): void {
+    checkValidWorkerOptions(opts)
+    this.opts = { ...DEFAULT_WORKER_OPTIONS, ...opts }
+  }
+
+  private endTaskPerformance (
+    taskPerformance: TaskPerformance
+  ): TaskPerformance {
+    if (this.statistics == null) {
+      throw new Error('Performance statistics computation requirements not set')
+    }
+    return {
+      ...taskPerformance,
+      ...(this.statistics.runTime && {
+        runTime: performance.now() - taskPerformance.timestamp,
+      }),
+      ...(this.statistics.elu && {
+        elu: performance.eventLoopUtilization(taskPerformance.elu),
+      }),
+    }
   }
 
   /**
-   * Handles an error and convert it to a string so it can be sent back to the main worker.
-   * @param error - The error raised by the worker.
-   * @returns The error message.
+   * Starts the worker check active interval.
    */
-  protected handleError (error: Error | string): string {
-    return error instanceof Error ? error.message : error
+  private startCheckActive (): void {
+    this.lastTaskTimestamp = performance.now()
+    this.activeInterval = setInterval(
+      this.checkActive.bind(this),
+      (this.opts.maxInactiveTime ?? DEFAULT_MAX_INACTIVE_TIME) / 2
+    )
   }
 
   /**
-   * Runs the given task.
-   * @param task - The task to execute.
+   * Stops the worker check active interval.
    */
-  protected readonly run = (task: Task<Data>): void => {
-    const { name, taskId, data } = task
-    const taskFunctionName = name ?? DEFAULT_TASK_NAME
-    if (!this.taskFunctions.has(taskFunctionName)) {
-      this.sendToMainWorker({
-        workerError: {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          name: name!,
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          message: `Task function '${name!}' not found`,
-          data,
-        },
-        taskId,
-      })
-      return
+  private stopCheckActive (): void {
+    if (this.activeInterval != null) {
+      clearInterval(this.activeInterval)
+      delete this.activeInterval
     }
-    const fn = this.taskFunctions.get(taskFunctionName)?.taskFunction
-    if (isAsyncFunction(fn)) {
-      this.runAsync(fn as TaskAsyncFunction<Data, Response>, task)
-    } else {
-      this.runSync(fn as TaskSyncFunction<Data, Response>, task)
+  }
+
+  private updateLastTaskTimestamp (): void {
+    if (this.activeInterval != null) {
+      this.lastTaskTimestamp = performance.now()
     }
   }
 
   /**
-   * Runs the given task function synchronously.
-   * @param fn - Task function that will be executed.
-   * @param task - Input data for the task function.
+   * Adds a task function to the worker.
+   * If a task function with the same name already exists, it is replaced.
+   * @param name - The name of the task function to add.
+   * @param fn - The task function to add.
+   * @returns Whether the task function was added or not.
    */
-  protected readonly runSync = (
-    fn: TaskSyncFunction<Data, Response>,
-    task: Task<Data>
-  ): void => {
-    const { name, taskId, data } = task
+  public addTaskFunction (
+    name: string,
+    fn: TaskFunction<Data, Response> | TaskFunctionObject<Data, Response>
+  ): TaskFunctionOperationResult {
     try {
-      let taskPerformance = this.beginTaskPerformance(name)
-      const res = fn(data)
-      taskPerformance = this.endTaskPerformance(taskPerformance)
-      this.sendToMainWorker({
-        data: res,
-        taskPerformance,
-        taskId,
-      })
+      checkTaskFunctionName(name)
+      if (name === DEFAULT_TASK_NAME) {
+        throw new Error(
+          'Cannot add a task function with the default reserved name'
+        )
+      }
+      if (typeof fn === 'function') {
+        fn = { taskFunction: fn } satisfies TaskFunctionObject<Data, Response>
+      }
+      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, fn)
+      }
+      this.taskFunctions.set(name, fn)
+      this.sendTaskFunctionsPropertiesToMainWorker()
+      return { status: true }
     } catch (error) {
-      this.sendToMainWorker({
-        workerError: {
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          name: name!,
-          message: this.handleError(error as Error | string),
-          data,
-        },
-        taskId,
-      })
-    } finally {
-      this.updateLastTaskTimestamp()
+      return { error: error as Error, status: false }
     }
   }
 
   /**
-   * Runs the given task function asynchronously.
-   * @param fn - Task function that will be executed.
-   * @param task - Input data for the task function.
+   * Checks if the worker has a task function with the given name.
+   * @param name - The name of the task function to check.
+   * @returns Whether the worker has a task function with the given name or not.
    */
-  protected readonly runAsync = (
-    fn: TaskAsyncFunction<Data, Response>,
-    task: Task<Data>
-  ): void => {
-    const { name, taskId, data } = task
-    let taskPerformance = this.beginTaskPerformance(name)
-    fn(data)
-      .then(res => {
-        taskPerformance = this.endTaskPerformance(taskPerformance)
-        this.sendToMainWorker({
-          data: res,
-          taskPerformance,
-          taskId,
-        })
-        return undefined
-      })
-      .catch((error: unknown) => {
-        this.sendToMainWorker({
-          workerError: {
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            name: name!,
-            message: this.handleError(error as Error | string),
-            data,
-          },
-          taskId,
-        })
-      })
-      .finally(() => {
-        this.updateLastTaskTimestamp()
-      })
-      .catch(EMPTY_FUNCTION)
+  public hasTaskFunction (name: string): TaskFunctionOperationResult {
+    try {
+      checkTaskFunctionName(name)
+    } catch (error) {
+      return { error: error as Error, status: false }
+    }
+    return { status: this.taskFunctions.has(name) }
   }
 
-  private beginTaskPerformance (name?: string): TaskPerformance {
-    if (this.statistics == null) {
-      throw new Error('Performance statistics computation requirements not set')
+  /**
+   * Lists the properties of the worker's task functions.
+   * @returns The properties of the worker's task functions.
+   */
+  public listTaskFunctionsProperties (): TaskFunctionProperties[] {
+    let defaultTaskFunctionName = DEFAULT_TASK_NAME
+    for (const [name, fnObj] of this.taskFunctions) {
+      if (
+        name !== DEFAULT_TASK_NAME &&
+        fnObj === this.taskFunctions.get(DEFAULT_TASK_NAME)
+      ) {
+        defaultTaskFunctionName = name
+        break
+      }
     }
-    return {
-      name: name ?? DEFAULT_TASK_NAME,
-      timestamp: performance.now(),
-      ...(this.statistics.elu && {
-        elu: performance.eventLoopUtilization(),
-      }),
+    const taskFunctionsProperties: TaskFunctionProperties[] = []
+    for (const [name, fnObj] of this.taskFunctions) {
+      if (name === DEFAULT_TASK_NAME || name === defaultTaskFunctionName) {
+        continue
+      }
+      taskFunctionsProperties.push(buildTaskFunctionProperties(name, fnObj))
     }
+    return [
+      buildTaskFunctionProperties(
+        DEFAULT_TASK_NAME,
+        this.taskFunctions.get(DEFAULT_TASK_NAME)
+      ),
+      buildTaskFunctionProperties(
+        defaultTaskFunctionName,
+        this.taskFunctions.get(defaultTaskFunctionName)
+      ),
+      ...taskFunctionsProperties,
+    ]
   }
 
-  private endTaskPerformance (
-    taskPerformance: TaskPerformance
-  ): TaskPerformance {
-    if (this.statistics == null) {
-      throw new Error('Performance statistics computation requirements not set')
-    }
-    return {
-      ...taskPerformance,
-      ...(this.statistics.runTime && {
-        runTime: performance.now() - taskPerformance.timestamp,
-      }),
-      ...(this.statistics.elu && {
-        elu: performance.eventLoopUtilization(taskPerformance.elu),
-      }),
+  /**
+   * Removes a task function from the worker.
+   * @param name - The name of the task function to remove.
+   * @returns Whether the task function existed and was removed or not.
+   */
+  public removeTaskFunction (name: string): TaskFunctionOperationResult {
+    try {
+      checkTaskFunctionName(name)
+      if (name === DEFAULT_TASK_NAME) {
+        throw new Error(
+          'Cannot remove the task function with the default reserved name'
+        )
+      }
+      if (
+        this.taskFunctions.get(name) ===
+        this.taskFunctions.get(DEFAULT_TASK_NAME)
+      ) {
+        throw new Error(
+          'Cannot remove the task function used as the default task function'
+        )
+      }
+      const deleteStatus = this.taskFunctions.delete(name)
+      this.sendTaskFunctionsPropertiesToMainWorker()
+      return { status: deleteStatus }
+    } catch (error) {
+      return { error: error as Error, status: false }
     }
   }
 
-  private updateLastTaskTimestamp (): void {
-    if (this.activeInterval != null) {
-      this.lastTaskTimestamp = performance.now()
+  /**
+   * Sets the default task function to use in the worker.
+   * @param name - The name of the task function to use as default task function.
+   * @returns Whether the default task function was set or not.
+   */
+  public setDefaultTaskFunction (name: string): TaskFunctionOperationResult {
+    try {
+      checkTaskFunctionName(name)
+      if (name === DEFAULT_TASK_NAME) {
+        throw new Error(
+          'Cannot set the default task function reserved name as the default task function'
+        )
+      }
+      if (!this.taskFunctions.has(name)) {
+        throw new Error(
+          'Cannot set the default task function to a non-existing task function'
+        )
+      }
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.taskFunctions.set(DEFAULT_TASK_NAME, this.taskFunctions.get(name)!)
+      this.sendTaskFunctionsPropertiesToMainWorker()
+      return { status: true }
+    } catch (error) {
+      return { error: error as Error, status: false }
     }
   }
 }
index d9f6e64756fa94d234023fcd200d3681cd621783..e6ffe0e25d0519cf1d7cc0f978fa30a2304b8f9f 100644 (file)
@@ -1,10 +1,11 @@
 import cluster, { type Worker } from 'node:cluster'
 
 import type { MessageValue } from '../utility-types.js'
-import { AbstractWorker } from './abstract-worker.js'
 import type { TaskFunction, TaskFunctions } from './task-functions.js'
 import type { WorkerOptions } from './worker-options.js'
 
+import { AbstractWorker } from './abstract-worker.js'
+
 /**
  * A cluster worker used by a poolifier `ClusterPool`.
  *
@@ -22,6 +23,16 @@ export class ClusterWorker<
   Data = unknown,
   Response = unknown
 > extends AbstractWorker<Worker, Data, Response> {
+  /** @inheritDoc */
+  protected readonly sendToMainWorker = (
+    message: MessageValue<Response>
+  ): void => {
+    this.getMainWorker().send({
+      ...message,
+      workerId: this.id,
+    } satisfies MessageValue<Response>)
+  }
+
   /**
    * Constructs a new poolifier cluster worker.
    * @param taskFunctions - Task function(s) processed by the worker when the pool's `execution` function is invoked.
@@ -56,14 +67,4 @@ export class ClusterWorker<
   protected get id (): number {
     return this.getMainWorker().id
   }
-
-  /** @inheritDoc */
-  protected readonly sendToMainWorker = (
-    message: MessageValue<Response>
-  ): void => {
-    this.getMainWorker().send({
-      ...message,
-      workerId: this.id,
-    } satisfies MessageValue<Response>)
-  }
 }
index 2fd951fb2ac03d71b820a1673561630f0c9d7736..9e4067304294337f5819d20293e31a3a719993d9 100644 (file)
@@ -30,8 +30,8 @@ export type TaskAsyncFunction<Data = unknown, Response = unknown> = (
  * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
  */
 export type TaskFunction<Data = unknown, Response = unknown> =
-  | TaskSyncFunction<Data, Response>
   | TaskAsyncFunction<Data, Response>
+  | TaskSyncFunction<Data, Response>
 
 /**
  * Task function object.
@@ -39,10 +39,6 @@ export type TaskFunction<Data = unknown, Response = unknown> =
  * @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.
    */
@@ -51,6 +47,10 @@ export interface TaskFunctionObject<Data = unknown, Response = unknown> {
    * Task function worker choice strategy.
    */
   strategy?: WorkerChoiceStrategy
+  /**
+   * Task function.
+   */
+  taskFunction: TaskFunction<Data, Response>
 }
 
 /**
@@ -69,6 +69,6 @@ export type TaskFunctions<Data = unknown, Response = unknown> = Record<
  * Task function operation result.
  */
 export interface TaskFunctionOperationResult {
-  status: boolean
   error?: Error
+  status: boolean
 }
index 8057d6f343caddda5091954cd1ed80e96f2e4b79..cd94753b5c977e8de64a65246c645981a4fd37c9 100644 (file)
@@ -6,10 +6,11 @@ import {
 } from 'node:worker_threads'
 
 import type { MessageValue } from '../utility-types.js'
-import { AbstractWorker } from './abstract-worker.js'
 import type { TaskFunction, TaskFunctions } from './task-functions.js'
 import type { WorkerOptions } from './worker-options.js'
 
+import { AbstractWorker } from './abstract-worker.js'
+
 /**
  * A thread worker used by a poolifier `ThreadPool`.
  *
@@ -27,6 +28,16 @@ export class ThreadWorker<
   Data = unknown,
   Response = unknown
 > extends AbstractWorker<MessagePort, Data, Response> {
+  /** @inheritDoc */
+  protected readonly sendToMainWorker = (
+    message: MessageValue<Response>
+  ): void => {
+    this.port?.postMessage({
+      ...message,
+      workerId: this.id,
+    } satisfies MessageValue<Response>)
+  }
+
   /**
    * Message port used to communicate with the main worker.
    */
@@ -44,6 +55,20 @@ export class ThreadWorker<
     super(isMainThread, parentPort, taskFunctions, opts)
   }
 
+  /**
+   * @inheritDoc
+   */
+  protected handleError (error: Error | string): string {
+    return error as string
+  }
+
+  /** @inheritDoc */
+  protected handleKillMessage (message: MessageValue<Data>): void {
+    super.handleKillMessage(message)
+    this.port?.unref()
+    this.port?.close()
+  }
+
   /** @inheritDoc */
   protected handleReadyMessage (message: MessageValue<Data>): void {
     if (
@@ -67,32 +92,8 @@ export class ThreadWorker<
     }
   }
 
-  /** @inheritDoc */
-  protected handleKillMessage (message: MessageValue<Data>): void {
-    super.handleKillMessage(message)
-    this.port?.unref()
-    this.port?.close()
-  }
-
   /** @inheritDoc */
   protected get id (): number {
     return threadId
   }
-
-  /** @inheritDoc */
-  protected readonly sendToMainWorker = (
-    message: MessageValue<Response>
-  ): void => {
-    this.port?.postMessage({
-      ...message,
-      workerId: this.id,
-    } satisfies MessageValue<Response>)
-  }
-
-  /**
-   * @inheritDoc
-   */
-  protected handleError (error: Error | string): string {
-    return error as string
-  }
 }
index 59a0289b79e78e9a9020257b872aca0c95486999..1b0ef6f9e7375c6f979f01b6928fa67cc30b98cd 100644 (file)
@@ -1,13 +1,14 @@
+import type { TaskFunctionObject } from './task-functions.js'
+
 import {
   checkValidPriority,
   checkValidWorkerChoiceStrategy,
 } from '../pools/utils.js'
 import { isPlainObject } from '../utils.js'
-import type { TaskFunctionObject } from './task-functions.js'
 import { KillBehaviors, type WorkerOptions } from './worker-options.js'
 
 export const checkValidWorkerOptions = (
-  opts: WorkerOptions | undefined
+  opts: undefined | WorkerOptions
 ): void => {
   if (opts != null && !isPlainObject(opts)) {
     throw new TypeError('opts worker options parameter is not a plain object')
index 92761840efda547c43a0c9758231c0c1d1b84bab..d2b8b93a2d823933320e2eb9a940022f5bbda40e 100644 (file)
@@ -1,16 +1,16 @@
 /**
  * Enumeration of kill behaviors.
  */
-export const KillBehaviors: Readonly<{ SOFT: 'SOFT'; HARD: 'HARD' }> =
+export const KillBehaviors: Readonly<{ HARD: 'HARD'; SOFT: 'SOFT' }> =
   Object.freeze({
-    /**
-     * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but the worker is stealing tasks or a task is executing or queued, then the worker **wont** be deleted.
-     */
-    SOFT: 'SOFT',
     /**
      * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but the worker is stealing tasks or a task is executing or queued, then the worker will be deleted.
      */
     HARD: 'HARD',
+    /**
+     * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but the worker is stealing tasks or a task is executing or queued, then the worker **wont** be deleted.
+     */
+    SOFT: 'SOFT',
   } as const)
 
 /**
@@ -21,7 +21,7 @@ export type KillBehavior = keyof typeof KillBehaviors
 /**
  * Handler called when a worker is killed.
  */
-export type KillHandler = () => void | Promise<void>
+export type KillHandler = () => Promise<void> | void
 
 /**
  * Options for workers.
@@ -37,6 +37,11 @@ export interface WorkerOptions {
    * @defaultValue KillBehaviors.SOFT
    */
   killBehavior?: KillBehavior
+  /**
+   * The function to call when a worker is killed.
+   * @defaultValue `() => {}`
+   */
+  killHandler?: KillHandler
   /**
    * Maximum waiting time in milliseconds for tasks on newly created workers. It must be greater or equal than 5.
    *
@@ -49,9 +54,4 @@ export interface WorkerOptions {
    * @defaultValue 60000
    */
   maxInactiveTime?: number
-  /**
-   * The function to call when a worker is killed.
-   * @defaultValue `() => {}`
-   */
-  killHandler?: KillHandler
 }
index ed4543dd4141bed999367e24c8b36b21c39748d5..0e933d7aaffd23bff7cf8088542c4e57f10e66e2 100644 (file)
@@ -1,11 +1,10 @@
+import { expect } from 'expect'
 // eslint-disable-next-line n/no-unsupported-features/node-builtins
 import { createHook, executionAsyncId } from 'node:async_hooks'
 import { EventEmitterAsyncResource } from 'node:events'
 import { readFileSync } from 'node:fs'
 import { dirname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
-
-import { expect } from 'expect'
 import { restore, stub } from 'sinon'
 
 import { CircularBuffer } from '../../lib/circular-buffer.cjs'
@@ -227,18 +226,18 @@ describe('Abstract pool test suite', () => {
     expect(pool.emitter).toBeInstanceOf(EventEmitterAsyncResource)
     expect(pool.emitter.eventNames()).toStrictEqual([])
     expect(pool.opts).toStrictEqual({
-      startWorkers: true,
       enableEvents: true,
-      restartWorkerOnError: true,
       enableTasksQueue: false,
+      restartWorkerOnError: true,
+      startWorkers: true,
       workerChoiceStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
+        elu: { median: false },
         runTime: { median: false },
         waitTime: { median: false },
-        elu: { median: false },
         weights: expect.objectContaining({
           0: expect.any(Number),
           [pool.info.maxSize - 1]: expect.any(Number),
@@ -251,51 +250,51 @@ describe('Abstract pool test suite', () => {
       numberOfWorkers,
       './tests/worker-files/thread/testWorker.mjs',
       {
+        enableEvents: false,
+        enableTasksQueue: true,
+        errorHandler: testHandler,
+        exitHandler: testHandler,
+        messageHandler: testHandler,
+        onlineHandler: testHandler,
+        restartWorkerOnError: false,
+        tasksQueueOptions: { concurrency: 2 },
         workerChoiceStrategy: WorkerChoiceStrategies.LEAST_USED,
         workerChoiceStrategyOptions: {
           runTime: { median: true },
           weights: { 0: 300, 1: 200 },
         },
-        enableEvents: false,
-        restartWorkerOnError: false,
-        enableTasksQueue: true,
-        tasksQueueOptions: { concurrency: 2 },
-        messageHandler: testHandler,
-        errorHandler: testHandler,
-        onlineHandler: testHandler,
-        exitHandler: testHandler,
       }
     )
     expect(pool.emitter).toBeUndefined()
     expect(pool.opts).toStrictEqual({
-      startWorkers: true,
       enableEvents: false,
-      restartWorkerOnError: false,
       enableTasksQueue: true,
+      errorHandler: testHandler,
+      exitHandler: testHandler,
+      messageHandler: testHandler,
+      onlineHandler: testHandler,
+      restartWorkerOnError: false,
+      startWorkers: true,
       tasksQueueOptions: {
         concurrency: 2,
         size: Math.pow(numberOfWorkers, 2),
-        taskStealing: true,
+        tasksFinishedTimeout: 2000,
         tasksStealingOnBackPressure: true,
         tasksStealingRatio: 0.6,
-        tasksFinishedTimeout: 2000,
+        taskStealing: true,
       },
       workerChoiceStrategy: WorkerChoiceStrategies.LEAST_USED,
       workerChoiceStrategyOptions: {
         runTime: { median: true },
         weights: { 0: 300, 1: 200 },
       },
-      onlineHandler: testHandler,
-      messageHandler: testHandler,
-      errorHandler: testHandler,
-      exitHandler: testHandler,
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
+        elu: { median: false },
         runTime: { median: true },
         waitTime: { median: false },
-        elu: { median: false },
         weights: { 0: 300, 1: 200 },
       })
     }
@@ -482,9 +481,9 @@ describe('Abstract pool test suite', () => {
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
+        elu: { median: false },
         runTime: { median: false },
         waitTime: { median: false },
-        elu: { median: false },
         weights: expect.objectContaining({
           0: expect.any(Number),
           [pool.info.maxSize - 1]: expect.any(Number),
@@ -494,36 +493,36 @@ describe('Abstract pool test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: true,
         average: true,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: true,
         average: true,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: true,
         average: true,
         median: false,
       },
     })
     pool.setWorkerChoiceStrategyOptions({
-      runTime: { median: true },
       elu: { median: true },
+      runTime: { median: true },
     })
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
-      runTime: { median: true },
       elu: { median: true },
+      runTime: { median: true },
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
+        elu: { median: true },
         runTime: { median: true },
         waitTime: { median: false },
-        elu: { median: true },
         weights: expect.objectContaining({
           0: expect.any(Number),
           [pool.info.maxSize - 1]: expect.any(Number),
@@ -533,6 +532,11 @@ describe('Abstract pool test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
+      elu: {
+        aggregate: true,
+        average: false,
+        median: true,
+      },
       runTime: {
         aggregate: true,
         average: false,
@@ -543,26 +547,21 @@ describe('Abstract pool test suite', () => {
         average: true,
         median: false,
       },
-      elu: {
-        aggregate: true,
-        average: false,
-        median: true,
-      },
     })
     pool.setWorkerChoiceStrategyOptions({
-      runTime: { median: false },
       elu: { median: false },
+      runTime: { median: false },
     })
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
-      runTime: { median: false },
       elu: { median: false },
+      runTime: { median: false },
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategiesContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
+        elu: { median: false },
         runTime: { median: false },
         waitTime: { median: false },
-        elu: { median: false },
         weights: expect.objectContaining({
           0: expect.any(Number),
           [pool.info.maxSize - 1]: expect.any(Number),
@@ -572,17 +571,17 @@ describe('Abstract pool test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: true,
         average: true,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: true,
         average: true,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: true,
         average: true,
         median: false,
@@ -622,20 +621,20 @@ describe('Abstract pool test suite', () => {
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 1,
       size: Math.pow(numberOfWorkers, 2),
-      taskStealing: true,
+      tasksFinishedTimeout: 2000,
       tasksStealingOnBackPressure: true,
       tasksStealingRatio: 0.6,
-      tasksFinishedTimeout: 2000,
+      taskStealing: true,
     })
     pool.enableTasksQueue(true, { concurrency: 2 })
     expect(pool.opts.enableTasksQueue).toBe(true)
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 2,
       size: Math.pow(numberOfWorkers, 2),
-      taskStealing: true,
+      tasksFinishedTimeout: 2000,
       tasksStealingOnBackPressure: true,
       tasksStealingRatio: 0.6,
-      tasksFinishedTimeout: 2000,
+      taskStealing: true,
     })
     pool.enableTasksQueue(false)
     expect(pool.opts.enableTasksQueue).toBe(false)
@@ -652,10 +651,10 @@ describe('Abstract pool test suite', () => {
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 1,
       size: Math.pow(numberOfWorkers, 2),
-      taskStealing: true,
+      tasksFinishedTimeout: 2000,
       tasksStealingOnBackPressure: true,
       tasksStealingRatio: 0.6,
-      tasksFinishedTimeout: 2000,
+      taskStealing: true,
     })
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.tasksQueueBackPressureSize).toBe(
@@ -665,18 +664,18 @@ describe('Abstract pool test suite', () => {
     pool.setTasksQueueOptions({
       concurrency: 2,
       size: 2,
-      taskStealing: false,
+      tasksFinishedTimeout: 3000,
       tasksStealingOnBackPressure: false,
       tasksStealingRatio: 0.5,
-      tasksFinishedTimeout: 3000,
+      taskStealing: false,
     })
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 2,
       size: 2,
-      taskStealing: false,
+      tasksFinishedTimeout: 3000,
       tasksStealingOnBackPressure: false,
       tasksStealingRatio: 0.5,
-      tasksFinishedTimeout: 3000,
+      taskStealing: false,
     })
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.tasksQueueBackPressureSize).toBe(
@@ -685,16 +684,16 @@ describe('Abstract pool test suite', () => {
     }
     pool.setTasksQueueOptions({
       concurrency: 1,
-      taskStealing: true,
       tasksStealingOnBackPressure: true,
+      taskStealing: true,
     })
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 1,
       size: 2,
-      taskStealing: true,
+      tasksFinishedTimeout: 3000,
       tasksStealingOnBackPressure: true,
       tasksStealingRatio: 0.5,
-      tasksFinishedTimeout: 3000,
+      taskStealing: true,
     })
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.tasksQueueBackPressureSize).toBe(
@@ -751,21 +750,21 @@ describe('Abstract pool test suite', () => {
       './tests/worker-files/thread/testWorker.mjs'
     )
     expect(pool.info).toStrictEqual({
-      version,
-      type: PoolTypes.fixed,
-      worker: WorkerTypes.thread,
-      started: true,
-      ready: true,
-      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
-      strategyRetries: 0,
-      minSize: numberOfWorkers,
-      maxSize: numberOfWorkers,
-      workerNodes: numberOfWorkers,
-      idleWorkerNodes: numberOfWorkers,
       busyWorkerNodes: 0,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       executedTasks: 0,
       executingTasks: 0,
       failedTasks: 0,
+      idleWorkerNodes: numberOfWorkers,
+      maxSize: numberOfWorkers,
+      minSize: numberOfWorkers,
+      ready: true,
+      started: true,
+      strategyRetries: 0,
+      type: PoolTypes.fixed,
+      version,
+      worker: WorkerTypes.thread,
+      workerNodes: numberOfWorkers,
     })
     await pool.destroy()
     pool = new DynamicClusterPool(
@@ -774,21 +773,21 @@ describe('Abstract pool test suite', () => {
       './tests/worker-files/cluster/testWorker.cjs'
     )
     expect(pool.info).toStrictEqual({
-      version,
-      type: PoolTypes.dynamic,
-      worker: WorkerTypes.cluster,
-      started: true,
-      ready: true,
-      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
-      strategyRetries: 0,
-      minSize: Math.floor(numberOfWorkers / 2),
-      maxSize: numberOfWorkers,
-      workerNodes: Math.floor(numberOfWorkers / 2),
-      idleWorkerNodes: Math.floor(numberOfWorkers / 2),
       busyWorkerNodes: 0,
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       executedTasks: 0,
       executingTasks: 0,
       failedTasks: 0,
+      idleWorkerNodes: Math.floor(numberOfWorkers / 2),
+      maxSize: numberOfWorkers,
+      minSize: Math.floor(numberOfWorkers / 2),
+      ready: true,
+      started: true,
+      strategyRetries: 0,
+      type: PoolTypes.dynamic,
+      version,
+      worker: WorkerTypes.cluster,
+      workerNodes: Math.floor(numberOfWorkers / 2),
     })
     await pool.destroy()
   })
@@ -801,29 +800,29 @@ describe('Abstract pool test suite', () => {
     for (const workerNode of pool.workerNodes) {
       expect(workerNode).toBeInstanceOf(WorkerNode)
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: 0,
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
     }
     await pool.destroy()
@@ -867,15 +866,15 @@ describe('Abstract pool test suite', () => {
     for (const workerNode of pool.workerNodes) {
       expect(workerNode).toBeInstanceOf(WorkerNode)
       expect(workerNode.info).toStrictEqual({
-        id: expect.any(Number),
-        type: WorkerTypes.cluster,
+        backPressure: false,
+        backPressureStealing: false,
+        continuousStealing: false,
         dynamic: false,
+        id: expect.any(Number),
         ready: true,
         stealing: false,
         stolen: false,
-        continuousStealing: false,
-        backPressureStealing: false,
-        backPressure: false,
+        type: WorkerTypes.cluster,
       })
     }
     await pool.destroy()
@@ -887,15 +886,15 @@ describe('Abstract pool test suite', () => {
     for (const workerNode of pool.workerNodes) {
       expect(workerNode).toBeInstanceOf(WorkerNode)
       expect(workerNode.info).toStrictEqual({
-        id: expect.any(Number),
-        type: WorkerTypes.thread,
+        backPressure: false,
+        backPressureStealing: false,
+        continuousStealing: false,
         dynamic: false,
+        id: expect.any(Number),
         ready: true,
         stealing: false,
         stolen: false,
-        continuousStealing: false,
-        backPressureStealing: false,
-        backPressure: false,
+        type: WorkerTypes.thread,
       })
     }
     await pool.destroy()
@@ -978,57 +977,57 @@ describe('Abstract pool test suite', () => {
     }
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: 0,
           executing: maxMultiplier,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
+      })
+    }
+    await Promise.all(promises)
+    for (const workerNode of pool.workerNodes) {
+      expect(workerNode.usage).toStrictEqual({
         elu: {
-          idle: {
+          active: {
             history: expect.any(CircularBuffer),
           },
-          active: {
+          idle: {
             history: expect.any(CircularBuffer),
           },
         },
-      })
-    }
-    await Promise.all(promises)
-    for (const workerNode of pool.workerNodes) {
-      expect(workerNode.usage).toStrictEqual({
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: maxMultiplier,
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
     }
     await pool.destroy()
@@ -1048,29 +1047,29 @@ describe('Abstract pool test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThan(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1080,29 +1079,29 @@ describe('Abstract pool test suite', () => {
     pool.setWorkerChoiceStrategy(WorkerChoiceStrategies.FAIR_SHARE)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThan(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1129,21 +1128,21 @@ describe('Abstract pool test suite', () => {
     expect(pool.emitter.eventNames()).toStrictEqual([PoolEvents.ready])
     expect(poolReady).toBe(1)
     expect(poolInfo).toStrictEqual({
-      version,
-      type: PoolTypes.dynamic,
-      worker: WorkerTypes.cluster,
-      started: true,
-      ready: true,
-      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
-      strategyRetries: expect.any(Number),
-      minSize: expect.any(Number),
-      maxSize: expect.any(Number),
-      workerNodes: expect.any(Number),
-      idleWorkerNodes: expect.any(Number),
       busyWorkerNodes: expect.any(Number),
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       executedTasks: expect.any(Number),
       executingTasks: expect.any(Number),
       failedTasks: expect.any(Number),
+      idleWorkerNodes: expect.any(Number),
+      maxSize: expect.any(Number),
+      minSize: expect.any(Number),
+      ready: true,
+      started: true,
+      strategyRetries: expect.any(Number),
+      type: PoolTypes.dynamic,
+      version,
+      worker: WorkerTypes.cluster,
+      workerNodes: expect.any(Number),
     })
     await pool.destroy()
   })
@@ -1170,21 +1169,21 @@ describe('Abstract pool test suite', () => {
     // So in total numberOfWorkers + 1 times for a loop submitting up to numberOfWorkers * 2 tasks to the fixed pool.
     expect(poolBusy).toBe(numberOfWorkers + 1)
     expect(poolInfo).toStrictEqual({
-      version,
-      type: PoolTypes.fixed,
-      worker: WorkerTypes.thread,
-      started: true,
-      ready: true,
-      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
-      strategyRetries: expect.any(Number),
-      minSize: expect.any(Number),
-      maxSize: expect.any(Number),
-      workerNodes: expect.any(Number),
-      idleWorkerNodes: expect.any(Number),
       busyWorkerNodes: expect.any(Number),
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       executedTasks: expect.any(Number),
       executingTasks: expect.any(Number),
       failedTasks: expect.any(Number),
+      idleWorkerNodes: expect.any(Number),
+      maxSize: expect.any(Number),
+      minSize: expect.any(Number),
+      ready: true,
+      started: true,
+      strategyRetries: expect.any(Number),
+      type: PoolTypes.fixed,
+      version,
+      worker: WorkerTypes.thread,
+      workerNodes: expect.any(Number),
     })
     await pool.destroy()
   })
@@ -1210,21 +1209,21 @@ describe('Abstract pool test suite', () => {
     await Promise.all(promises)
     expect(poolFull).toBe(1)
     expect(poolInfo).toStrictEqual({
-      version,
-      type: PoolTypes.dynamic,
-      worker: WorkerTypes.thread,
-      started: true,
-      ready: true,
-      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
-      strategyRetries: expect.any(Number),
-      minSize: expect.any(Number),
-      maxSize: expect.any(Number),
-      workerNodes: expect.any(Number),
-      idleWorkerNodes: expect.any(Number),
       busyWorkerNodes: expect.any(Number),
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       executedTasks: expect.any(Number),
       executingTasks: expect.any(Number),
       failedTasks: expect.any(Number),
+      idleWorkerNodes: expect.any(Number),
+      maxSize: expect.any(Number),
+      minSize: expect.any(Number),
+      ready: true,
+      started: true,
+      strategyRetries: expect.any(Number),
+      type: PoolTypes.dynamic,
+      version,
+      worker: WorkerTypes.thread,
+      workerNodes: expect.any(Number),
     })
     await pool.destroy()
   })
@@ -1253,27 +1252,27 @@ describe('Abstract pool test suite', () => {
     await Promise.all(promises)
     expect(poolBackPressure).toBe(1)
     expect(poolInfo).toStrictEqual({
-      version,
-      type: PoolTypes.fixed,
-      worker: WorkerTypes.thread,
-      started: true,
-      ready: true,
-      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
-      strategyRetries: expect.any(Number),
-      minSize: expect.any(Number),
-      maxSize: expect.any(Number),
-      workerNodes: expect.any(Number),
-      idleWorkerNodes: expect.any(Number),
-      busyWorkerNodes: expect.any(Number),
-      stealingWorkerNodes: expect.any(Number),
+      backPressure: true,
       backPressureWorkerNodes: expect.any(Number),
+      busyWorkerNodes: expect.any(Number),
+      defaultStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
       executedTasks: expect.any(Number),
       executingTasks: expect.any(Number),
+      failedTasks: expect.any(Number),
+      idleWorkerNodes: expect.any(Number),
       maxQueuedTasks: expect.any(Number),
+      maxSize: expect.any(Number),
+      minSize: expect.any(Number),
       queuedTasks: expect.any(Number),
-      backPressure: true,
+      ready: true,
+      started: true,
+      stealingWorkerNodes: expect.any(Number),
       stolenTasks: expect.any(Number),
-      failedTasks: expect.any(Number),
+      strategyRetries: expect.any(Number),
+      type: PoolTypes.fixed,
+      version,
+      worker: WorkerTypes.thread,
+      workerNodes: expect.any(Number),
     })
     expect(pool.hasBackPressure.callCount).toBeGreaterThanOrEqual(7)
     await pool.destroy()
@@ -1343,18 +1342,18 @@ describe('Abstract pool test suite', () => {
     let afterCalls = 0
     let resolveCalls = 0
     const hook = createHook({
+      after (asyncId) {
+        if (asyncId === taskAsyncId) afterCalls++
+      },
+      before (asyncId) {
+        if (asyncId === taskAsyncId) beforeCalls++
+      },
       init (asyncId, type) {
         if (type === 'poolifier:task') {
           initCalls++
           taskAsyncId = asyncId
         }
       },
-      before (asyncId) {
-        if (asyncId === taskAsyncId) beforeCalls++
-      },
-      after (asyncId) {
-        if (asyncId === taskAsyncId) afterCalls++
-      },
       promiseResolve () {
         if (executionAsyncId() === taskAsyncId) resolveCalls++
       },
@@ -1432,24 +1431,24 @@ describe('Abstract pool test suite', () => {
     ).rejects.toThrow(new TypeError('taskFunction property must be a function'))
     await expect(
       dynamicThreadPool.addTaskFunction('test', {
-        taskFunction: () => {},
         priority: -21,
+        taskFunction: () => {},
       })
     ).rejects.toThrow(
       new RangeError("Property 'priority' must be between -20 and 19")
     )
     await expect(
       dynamicThreadPool.addTaskFunction('test', {
-        taskFunction: () => {},
         priority: 20,
+        taskFunction: () => {},
       })
     ).rejects.toThrow(
       new RangeError("Property 'priority' must be between -20 and 19")
     )
     await expect(
       dynamicThreadPool.addTaskFunction('test', {
-        taskFunction: () => {},
         strategy: 'invalidStrategy',
+        taskFunction: () => {},
       })
     ).rejects.toThrow(
       new Error("Invalid worker choice strategy 'invalidStrategy'")
@@ -1466,14 +1465,14 @@ describe('Abstract pool test suite', () => {
     }
     await expect(
       dynamicThreadPool.addTaskFunction('echo', {
-        taskFunction: echoTaskFunction,
         strategy: WorkerChoiceStrategies.LEAST_ELU,
+        taskFunction: echoTaskFunction,
       })
     ).resolves.toBe(true)
     expect(dynamicThreadPool.taskFunctions.size).toBe(1)
     expect(dynamicThreadPool.taskFunctions.get('echo')).toStrictEqual({
-      taskFunction: echoTaskFunction,
       strategy: WorkerChoiceStrategies.LEAST_ELU,
+      taskFunction: echoTaskFunction,
     })
     expect([
       ...dynamicThreadPool.workerChoiceStrategiesContext.workerChoiceStrategies.keys(),
@@ -1491,28 +1490,28 @@ describe('Abstract pool test suite', () => {
     expect(echoResult).toStrictEqual(taskFunctionData)
     for (const workerNode of dynamicThreadPool.workerNodes) {
       expect(workerNode.getTaskFunctionWorkerUsage('echo')).toStrictEqual({
+        elu: expect.objectContaining({
+          active: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+          idle: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+        }),
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
+          failed: 0,
           queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: expect.objectContaining({
-          idle: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-          active: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-        }),
       })
       expect(
         workerNode.getTaskFunctionWorkerUsage('echo').tasks.executed
@@ -1576,13 +1575,13 @@ describe('Abstract pool test suite', () => {
       return data
     }
     await dynamicThreadPool.addTaskFunction('echo', {
-      taskFunction: echoTaskFunction,
       strategy: WorkerChoiceStrategies.LEAST_ELU,
+      taskFunction: echoTaskFunction,
     })
     expect(dynamicThreadPool.taskFunctions.size).toBe(1)
     expect(dynamicThreadPool.taskFunctions.get('echo')).toStrictEqual({
-      taskFunction: echoTaskFunction,
       strategy: WorkerChoiceStrategies.LEAST_ELU,
+      taskFunction: echoTaskFunction,
     })
     expect([
       ...dynamicThreadPool.workerChoiceStrategiesContext.workerChoiceStrategies.keys(),
@@ -1619,9 +1618,9 @@ describe('Abstract pool test suite', () => {
     await waitPoolEvents(dynamicThreadPool, PoolEvents.ready, 1)
     expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
       { name: DEFAULT_TASK_NAME },
-      { name: 'jsonIntegerSerialization' },
       { name: 'factorial' },
       { name: 'fibonacci' },
+      { name: 'jsonIntegerSerialization' },
     ])
     await dynamicThreadPool.destroy()
     const fixedClusterPool = new FixedClusterPool(
@@ -1631,9 +1630,9 @@ describe('Abstract pool test suite', () => {
     await waitPoolEvents(fixedClusterPool, PoolEvents.ready, 1)
     expect(fixedClusterPool.listTaskFunctionsProperties()).toStrictEqual([
       { name: DEFAULT_TASK_NAME },
-      { name: 'jsonIntegerSerialization' },
       { name: 'factorial' },
       { name: 'fibonacci' },
+      { name: 'jsonIntegerSerialization' },
     ])
     await fixedClusterPool.destroy()
   })
@@ -1667,9 +1666,9 @@ describe('Abstract pool test suite', () => {
     )
     expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
       { name: DEFAULT_TASK_NAME },
-      { name: 'jsonIntegerSerialization' },
       { name: 'factorial' },
       { name: 'fibonacci' },
+      { name: 'jsonIntegerSerialization' },
     ])
     await expect(
       dynamicThreadPool.setDefaultTaskFunction('factorial')
@@ -1677,8 +1676,8 @@ describe('Abstract pool test suite', () => {
     expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
       { name: DEFAULT_TASK_NAME },
       { name: 'factorial' },
-      { name: 'jsonIntegerSerialization' },
       { name: 'fibonacci' },
+      { name: 'jsonIntegerSerialization' },
     ])
     await expect(
       dynamicThreadPool.setDefaultTaskFunction('fibonacci')
@@ -1686,8 +1685,8 @@ describe('Abstract pool test suite', () => {
     expect(dynamicThreadPool.listTaskFunctionsProperties()).toStrictEqual([
       { name: DEFAULT_TASK_NAME },
       { name: 'fibonacci' },
-      { name: 'jsonIntegerSerialization' },
       { name: 'factorial' },
+      { name: 'jsonIntegerSerialization' },
     ])
     await dynamicThreadPool.destroy()
   })
@@ -1700,7 +1699,7 @@ describe('Abstract pool test suite', () => {
     )
     const data = { n: 10 }
     const result0 = await pool.execute(data)
-    expect(result0).toStrictEqual({ ok: 1 })
+    expect(result0).toStrictEqual(3628800)
     const result1 = await pool.execute(data, 'jsonIntegerSerialization')
     expect(result1).toStrictEqual({ ok: 1 })
     const result2 = await pool.execute(data, 'factorial')
@@ -1712,9 +1711,9 @@ describe('Abstract pool test suite', () => {
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.info.taskFunctionsProperties).toStrictEqual([
         { name: DEFAULT_TASK_NAME },
-        { name: 'jsonIntegerSerialization' },
         { name: 'factorial' },
         { name: 'fibonacci' },
+        { name: 'jsonIntegerSerialization' },
       ])
       expect(workerNode.taskFunctionsUsage.size).toBe(3)
       expect(workerNode.usage.tasks.executed).toBeGreaterThan(0)
@@ -1723,6 +1722,17 @@ describe('Abstract pool test suite', () => {
         expect(
           workerNode.getTaskFunctionWorkerUsage(taskFunctionProperties.name)
         ).toStrictEqual({
+          elu: {
+            active: {
+              history: expect.any(CircularBuffer),
+            },
+            idle: {
+              history: expect.any(CircularBuffer),
+            },
+          },
+          runTime: {
+            history: expect.any(CircularBuffer),
+          },
           tasks: {
             executed: expect.any(Number),
             executing: 0,
@@ -1731,20 +1741,9 @@ describe('Abstract pool test suite', () => {
             sequentiallyStolen: 0,
             stolen: 0,
           },
-          runTime: {
-            history: expect.any(CircularBuffer),
-          },
           waitTime: {
             history: expect.any(CircularBuffer),
           },
-          elu: {
-            idle: {
-              history: expect.any(CircularBuffer),
-            },
-            active: {
-              history: expect.any(CircularBuffer),
-            },
-          },
         })
         expect(
           workerNode.getTaskFunctionWorkerUsage(taskFunctionProperties.name)
@@ -1786,7 +1785,10 @@ describe('Abstract pool test suite', () => {
     await expect(pool.mapExecute([undefined], 'unknown')).rejects.toBe(
       "Task function 'unknown' not found"
     )
-    let results = await pool.mapExecute([{}, {}, {}, {}])
+    let results = await pool.mapExecute(
+      [{}, {}, {}, {}],
+      'jsonIntegerSerialization'
+    )
     expect(results).toStrictEqual([{ ok: 1 }, { ok: 1 }, { ok: 1 }, { ok: 1 }])
     expect(pool.info.executingTasks).toBe(0)
     expect(pool.info.executedTasks).toBe(4)
@@ -1822,7 +1824,7 @@ describe('Abstract pool test suite', () => {
     )
     const data = { n: 10 }
     const result0 = await pool.execute(data)
-    expect(result0).toStrictEqual({ ok: 1 })
+    expect(result0).toStrictEqual(3628800)
     const result1 = await pool.execute(data, 'jsonIntegerSerialization')
     expect(result1).toStrictEqual({ ok: 1 })
     const result2 = await pool.execute(data, 'factorial')
@@ -1834,9 +1836,9 @@ describe('Abstract pool test suite', () => {
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.info.taskFunctionsProperties).toStrictEqual([
         { name: DEFAULT_TASK_NAME },
-        { name: 'jsonIntegerSerialization' },
         { name: 'factorial' },
         { name: 'fibonacci', priority: -5 },
+        { name: 'jsonIntegerSerialization' },
       ])
       expect(workerNode.taskFunctionsUsage.size).toBe(3)
       expect(workerNode.usage.tasks.executed).toBeGreaterThan(0)
@@ -1845,6 +1847,17 @@ describe('Abstract pool test suite', () => {
         expect(
           workerNode.getTaskFunctionWorkerUsage(taskFunctionProperties.name)
         ).toStrictEqual({
+          elu: {
+            active: {
+              history: expect.any(CircularBuffer),
+            },
+            idle: {
+              history: expect.any(CircularBuffer),
+            },
+          },
+          runTime: {
+            history: expect.any(CircularBuffer),
+          },
           tasks: {
             executed: expect.any(Number),
             executing: 0,
@@ -1853,20 +1866,9 @@ describe('Abstract pool test suite', () => {
             sequentiallyStolen: 0,
             stolen: 0,
           },
-          runTime: {
-            history: expect.any(CircularBuffer),
-          },
           waitTime: {
             history: expect.any(CircularBuffer),
           },
-          elu: {
-            idle: {
-              history: expect.any(CircularBuffer),
-            },
-            active: {
-              history: expect.any(CircularBuffer),
-            },
-          },
         })
         expect(
           workerNode.getTaskFunctionWorkerUsage(taskFunctionProperties.name)
@@ -1906,9 +1908,9 @@ describe('Abstract pool test suite', () => {
     const workerNodeKey = 0
     await expect(
       pool.sendTaskFunctionOperationToWorker(workerNodeKey, {
+        taskFunction: (() => {}).toString(),
         taskFunctionOperation: 'add',
         taskFunctionProperties: { name: 'empty' },
-        taskFunction: (() => {}).toString(),
       })
     ).resolves.toBe(true)
     expect(
@@ -1929,9 +1931,9 @@ describe('Abstract pool test suite', () => {
     )
     await expect(
       pool.sendTaskFunctionOperationToWorkers({
+        taskFunction: (() => {}).toString(),
         taskFunctionOperation: 'add',
         taskFunctionProperties: { name: 'empty' },
-        taskFunction: (() => {}).toString(),
       })
     ).resolves.toBe(true)
     for (const workerNode of pool.workerNodes) {
index c93a67116ab35fe51750c005c32c3fb67bf156c2..2ac250ca411e4f4c59c7d92fc97094d99e385d22 100644 (file)
@@ -99,8 +99,8 @@ describe('Dynamic cluster pool test suite', () => {
       './tests/worker-files/cluster/longRunningWorkerHardBehavior.cjs',
       {
         errorHandler: e => console.error(e),
-        onlineHandler: () => console.info('long executing worker is online'),
         exitHandler: () => console.info('long executing worker exited'),
+        onlineHandler: () => console.info('long executing worker is online'),
       }
     )
     expect(longRunningPool.workerNodes.length).toBe(min)
@@ -127,8 +127,8 @@ describe('Dynamic cluster pool test suite', () => {
       './tests/worker-files/cluster/longRunningWorkerSoftBehavior.cjs',
       {
         errorHandler: e => console.error(e),
-        onlineHandler: () => console.info('long executing worker is online'),
         exitHandler: () => console.info('long executing worker exited'),
+        onlineHandler: () => console.info('long executing worker is online'),
       }
     )
     expect(longRunningPool.workerNodes.length).toBe(min)
index 68808f60d582ae786c13292b776b00d14ec3c832..30bce79e7108714bc457e3b159ab681969f36878 100644 (file)
@@ -1,6 +1,5 @@
-import cluster from 'node:cluster'
-
 import { expect } from 'expect'
+import cluster from 'node:cluster'
 
 import { FixedClusterPool, PoolEvents } from '../../../lib/index.cjs'
 import { DEFAULT_TASK_NAME } from '../../../lib/utils.cjs'
@@ -10,7 +9,7 @@ import { waitPoolEvents, waitWorkerEvents } from '../../test-utils.cjs'
 describe('Fixed cluster pool test suite', () => {
   const numberOfWorkers = 8
   const tasksConcurrency = 2
-  let pool, queuePool, emptyPool, echoPool, errorPool, asyncErrorPool, asyncPool
+  let asyncErrorPool, asyncPool, echoPool, emptyPool, errorPool, pool, queuePool
 
   before('Create pools', () => {
     pool = new FixedClusterPool(
@@ -25,10 +24,10 @@ describe('Fixed cluster pool test suite', () => {
       './tests/worker-files/cluster/testWorker.cjs',
       {
         enableTasksQueue: true,
+        errorHandler: e => console.error(e),
         tasksQueueOptions: {
           concurrency: tasksConcurrency,
         },
-        errorHandler: e => console.error(e),
       }
     )
     emptyPool = new FixedClusterPool(
@@ -213,9 +212,9 @@ describe('Fixed cluster pool test suite', () => {
     expect(typeof inError === 'string').toBe(true)
     expect(inError).toBe('Error Message from ClusterWorker')
     expect(taskError).toStrictEqual({
-      name: DEFAULT_TASK_NAME,
-      message: 'Error Message from ClusterWorker',
       data,
+      message: 'Error Message from ClusterWorker',
+      name: DEFAULT_TASK_NAME,
     })
     expect(
       errorPool.workerNodes.some(
@@ -244,9 +243,9 @@ describe('Fixed cluster pool test suite', () => {
     expect(typeof inError === 'string').toBe(true)
     expect(inError).toBe('Error Message from ClusterWorker:async')
     expect(taskError).toStrictEqual({
-      name: DEFAULT_TASK_NAME,
-      message: 'Error Message from ClusterWorker:async',
       data,
+      message: 'Error Message from ClusterWorker:async',
+      name: DEFAULT_TASK_NAME,
     })
     expect(
       asyncErrorPool.workerNodes.some(
@@ -307,8 +306,8 @@ describe('Fixed cluster pool test suite', () => {
     })
     expect(cluster.settings).toMatchObject({
       args: ['--use', 'http'],
-      silent: true,
       exec: workerFilePath,
+      silent: true,
     })
     await pool.destroy()
   })
index f2223b8358e1f303e4a64f4fed9594c9c7953145..2968f8946daa122ddf0bae9a5eac5911237552b3 100644 (file)
@@ -29,18 +29,18 @@ describe('Selection strategies utils test suite', () => {
 
   it('Verify buildWorkerChoiceStrategyOptions() behavior', async () => {
     expect(buildWorkerChoiceStrategyOptions(clusterFixedPool)).toStrictEqual({
+      elu: { median: false },
       runTime: { median: false },
       waitTime: { median: false },
-      elu: { median: false },
       weights: expect.objectContaining({
         0: expect.any(Number),
         [clusterFixedPool.info.maxSize - 1]: expect.any(Number),
       }),
     })
     const workerChoiceStrategyOptions = {
+      elu: { median: true },
       runTime: { median: true },
       waitTime: { median: true },
-      elu: { median: true },
       weights: {
         0: 100,
         1: 100,
@@ -59,9 +59,9 @@ describe('Selection strategies utils test suite', () => {
       threadFixedPool.info.maxSize * 2
     )
     const workerChoiceStrategyOptions = {
+      elu: { median: true },
       runTime: { median: true },
       waitTime: { median: true },
-      elu: { median: true },
       weights: {
         0: 100,
         1: 100,
index e8df7e1a5c35baa931595bb49493f5ba28e4fd5c..6a1aab6bdc11b127c8b287ec66a7e0ff832d8041 100644 (file)
@@ -1,6 +1,5 @@
-import { randomInt } from 'node:crypto'
-
 import { expect } from 'expect'
+import { randomInt } from 'node:crypto'
 
 import { CircularBuffer } from '../../../lib/circular-buffer.cjs'
 import {
@@ -158,8 +157,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -169,8 +168,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -186,17 +185,17 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: false,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: false,
         average: false,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: false,
         average: false,
         median: false,
@@ -212,17 +211,17 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: false,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: false,
         average: false,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: false,
         average: false,
         median: false,
@@ -248,29 +247,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: maxMultiplier,
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
     }
     expect(
@@ -304,29 +303,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -433,8 +432,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -444,8 +443,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -461,17 +460,17 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: false,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: false,
         average: false,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: false,
         average: false,
         median: false,
@@ -487,17 +486,17 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: false,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: false,
         average: false,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: false,
         average: false,
         median: false,
@@ -522,29 +521,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -581,29 +580,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -632,8 +631,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -643,8 +642,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -660,18 +659,18 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
-        aggregate: true,
+      elu: {
+        aggregate: false,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: true,
         average: false,
         median: false,
       },
-      elu: {
-        aggregate: false,
+      waitTime: {
+        aggregate: true,
         average: false,
         median: false,
       },
@@ -686,18 +685,18 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
-        aggregate: true,
+      elu: {
+        aggregate: false,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: true,
         average: false,
         median: false,
       },
-      elu: {
-        aggregate: false,
+      waitTime: {
+        aggregate: true,
         average: false,
         median: false,
       },
@@ -721,29 +720,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -790,29 +789,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -851,8 +850,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -862,8 +861,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -879,18 +878,18 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
-        aggregate: false,
+      elu: {
+        aggregate: true,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: false,
         average: false,
         median: false,
       },
-      elu: {
-        aggregate: true,
+      waitTime: {
+        aggregate: false,
         average: false,
         median: false,
       },
@@ -905,18 +904,18 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
-        aggregate: false,
+      elu: {
+        aggregate: true,
         average: false,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: false,
         average: false,
         median: false,
       },
-      elu: {
-        aggregate: true,
+      waitTime: {
+        aggregate: false,
         average: false,
         median: false,
       },
@@ -940,29 +939,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: expect.objectContaining({
+          active: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+          idle: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+        }),
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: expect.objectContaining({
-          idle: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-          active: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-        }),
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1015,29 +1014,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: expect.objectContaining({
+          active: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+          idle: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+        }),
+        runTime: {
+          history: expect.any(CircularBuffer),
+        },
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
-        },
-        runTime: {
-          history: expect.any(CircularBuffer),
         },
         waitTime: {
           history: expect.any(CircularBuffer),
         },
-        elu: expect.objectContaining({
-          idle: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-          active: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-        }),
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1082,8 +1081,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -1093,8 +1092,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1110,17 +1109,17 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: true,
         average: true,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: true,
         average: true,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: true,
         average: true,
         median: false,
@@ -1136,17 +1135,17 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
-      runTime: {
+      elu: {
         aggregate: true,
         average: true,
         median: false,
       },
-      waitTime: {
+      runTime: {
         aggregate: true,
         average: true,
         median: false,
       },
-      elu: {
+      waitTime: {
         aggregate: true,
         average: true,
         median: false,
@@ -1171,29 +1170,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: expect.objectContaining({
+          active: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+          idle: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+        }),
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: expect.objectContaining({
-          idle: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-          active: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-        }),
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1267,29 +1266,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: expect.objectContaining({
+          active: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+          idle: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+        }),
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: expect.objectContaining({
-          idle: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-          active: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-        }),
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1368,29 +1367,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: expect.objectContaining({
+          active: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+          idle: expect.objectContaining({
+            history: expect.any(CircularBuffer),
+          }),
+        }),
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: expect.objectContaining({
-          idle: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-          active: expect.objectContaining({
-            history: expect.any(CircularBuffer),
-          }),
-        }),
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1490,8 +1489,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -1501,8 +1500,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1518,6 +1517,11 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
+      elu: {
+        aggregate: false,
+        average: false,
+        median: false,
+      },
       runTime: {
         aggregate: true,
         average: true,
@@ -1528,11 +1532,6 @@ describe('Selection strategies test suite', () => {
         average: true,
         median: false,
       },
-      elu: {
-        aggregate: false,
-        average: false,
-        median: false,
-      },
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -1544,6 +1543,11 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
+      elu: {
+        aggregate: false,
+        average: false,
+        median: false,
+      },
       runTime: {
         aggregate: true,
         average: true,
@@ -1554,11 +1558,6 @@ describe('Selection strategies test suite', () => {
         average: true,
         median: false,
       },
-      elu: {
-        aggregate: false,
-        average: false,
-        median: false,
-      },
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1579,29 +1578,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1663,29 +1662,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1752,29 +1751,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -1897,8 +1896,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -1908,8 +1907,8 @@ describe('Selection strategies test suite', () => {
       { workerChoiceStrategy }
     )
     expect(pool.workerChoiceStrategiesContext.getPolicy()).toStrictEqual({
-      dynamicWorkerUsage: false,
       dynamicWorkerReady: true,
+      dynamicWorkerUsage: false,
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1926,6 +1925,11 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
+      elu: {
+        aggregate: false,
+        average: false,
+        median: false,
+      },
       runTime: {
         aggregate: true,
         average: true,
@@ -1936,11 +1940,6 @@ describe('Selection strategies test suite', () => {
         average: true,
         median: false,
       },
-      elu: {
-        aggregate: false,
-        average: false,
-        median: false,
-      },
     })
     await pool.destroy()
     pool = new DynamicThreadPool(
@@ -1952,6 +1951,11 @@ describe('Selection strategies test suite', () => {
     expect(
       pool.workerChoiceStrategiesContext.getTaskStatisticsRequirements()
     ).toStrictEqual({
+      elu: {
+        aggregate: false,
+        average: false,
+        median: false,
+      },
       runTime: {
         aggregate: true,
         average: true,
@@ -1962,11 +1966,6 @@ describe('Selection strategies test suite', () => {
         average: true,
         median: false,
       },
-      elu: {
-        aggregate: false,
-        average: false,
-        median: false,
-      },
     })
     // We need to clean up the resources after our test
     await pool.destroy()
@@ -1990,29 +1989,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
@@ -2094,29 +2093,29 @@ describe('Selection strategies test suite', () => {
     await Promise.all(promises)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.usage).toStrictEqual({
+        elu: {
+          active: {
+            history: expect.any(CircularBuffer),
+          },
+          idle: {
+            history: expect.any(CircularBuffer),
+          },
+        },
+        runTime: expect.objectContaining({
+          history: expect.any(CircularBuffer),
+        }),
         tasks: {
           executed: expect.any(Number),
           executing: 0,
-          queued: 0,
+          failed: 0,
           maxQueued: 0,
+          queued: 0,
           sequentiallyStolen: 0,
           stolen: 0,
-          failed: 0,
         },
-        runTime: expect.objectContaining({
-          history: expect.any(CircularBuffer),
-        }),
         waitTime: expect.objectContaining({
           history: expect.any(CircularBuffer),
         }),
-        elu: {
-          idle: {
-            history: expect.any(CircularBuffer),
-          },
-          active: {
-            history: expect.any(CircularBuffer),
-          },
-        },
       })
       expect(workerNode.usage.tasks.executed).toBeGreaterThanOrEqual(0)
       expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
index fae2aecb1ad21c9195db468d342c370f6dadc385..18a67bd7195e8f3b157842b1c7e021708d78642d 100644 (file)
@@ -1,6 +1,5 @@
-import { randomInt } from 'node:crypto'
-
 import { expect } from 'expect'
+import { randomInt } from 'node:crypto'
 
 import { FixedThreadPool } from '../../../lib/index.cjs'
 import { InterleavedWeightedRoundRobinWorkerChoiceStrategy } from '../../../lib/pools/selection-strategies/interleaved-weighted-round-robin-worker-choice-strategy.cjs'
index 427103a45c45b6429256e75fdd7376a61e24de70..c653d9f5470edc21281ba9998d6cde0f58e7f398 100644 (file)
@@ -18,7 +18,7 @@ import { WorkerChoiceStrategiesContext } from '../../../lib/pools/selection-stra
 describe('Worker choice strategies context test suite', () => {
   const min = 1
   const max = 3
-  let fixedPool, dynamicPool
+  let dynamicPool, fixedPool
 
   before('Create pools', () => {
     fixedPool = new FixedThreadPool(
index ab2d7f9c54daa173b75b12788f34a7e9a05b72cc..1c626d61a1b8210493f9a934e1ec1bfca0698b3e 100644 (file)
@@ -99,8 +99,8 @@ describe('Dynamic thread pool test suite', () => {
       './tests/worker-files/thread/longRunningWorkerHardBehavior.mjs',
       {
         errorHandler: e => console.error(e),
-        onlineHandler: () => console.info('long executing worker is online'),
         exitHandler: () => console.info('long executing worker exited'),
+        onlineHandler: () => console.info('long executing worker is online'),
       }
     )
     expect(longRunningPool.workerNodes.length).toBe(min)
@@ -127,8 +127,8 @@ describe('Dynamic thread pool test suite', () => {
       './tests/worker-files/thread/longRunningWorkerSoftBehavior.mjs',
       {
         errorHandler: e => console.error(e),
-        onlineHandler: () => console.info('long executing worker is online'),
         exitHandler: () => console.info('long executing worker exited'),
+        onlineHandler: () => console.info('long executing worker is online'),
       }
     )
     expect(longRunningPool.workerNodes.length).toBe(min)
index 15698ae70b6ba983d83b3eb2e537ba76c71453a7..78ef51ff013f45e9b548fb23ea83f8f47e6085b6 100644 (file)
@@ -8,7 +8,7 @@ import { waitPoolEvents, waitWorkerEvents } from '../../test-utils.cjs'
 describe('Fixed thread pool test suite', () => {
   const numberOfThreads = 6
   const tasksConcurrency = 2
-  let pool, queuePool, emptyPool, echoPool, errorPool, asyncErrorPool, asyncPool
+  let asyncErrorPool, asyncPool, echoPool, emptyPool, errorPool, pool, queuePool
 
   before('Create pools', () => {
     pool = new FixedThreadPool(
@@ -23,10 +23,10 @@ describe('Fixed thread pool test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       {
         enableTasksQueue: true,
+        errorHandler: e => console.error(e),
         tasksQueueOptions: {
           concurrency: tasksConcurrency,
         },
-        errorHandler: e => console.error(e),
       }
     )
     emptyPool = new FixedThreadPool(
@@ -240,9 +240,9 @@ describe('Fixed thread pool test suite', () => {
     expect(typeof inError.message === 'string').toBe(true)
     expect(inError.message).toBe('Error Message from ThreadWorker')
     expect(taskError).toStrictEqual({
-      name: DEFAULT_TASK_NAME,
-      message: new Error('Error Message from ThreadWorker'),
       data,
+      message: new Error('Error Message from ThreadWorker'),
+      name: DEFAULT_TASK_NAME,
     })
     expect(
       errorPool.workerNodes.some(
@@ -273,9 +273,9 @@ describe('Fixed thread pool test suite', () => {
     expect(typeof inError.message === 'string').toBe(true)
     expect(inError.message).toBe('Error Message from ThreadWorker:async')
     expect(taskError).toStrictEqual({
-      name: DEFAULT_TASK_NAME,
-      message: new Error('Error Message from ThreadWorker:async'),
       data,
+      message: new Error('Error Message from ThreadWorker:async'),
+      name: DEFAULT_TASK_NAME,
     })
     expect(
       asyncErrorPool.workerNodes.some(
index b2265b03cb51ce9dab9ef873f3efad05e6c776b3..36862a2a59215760595e54ccbd7af0e64396b6f4 100644 (file)
@@ -1,8 +1,7 @@
+import { expect } from 'expect'
 import cluster, { Worker as ClusterWorker } from 'node:cluster'
 import { Worker as ThreadWorker } from 'node:worker_threads'
 
-import { expect } from 'expect'
-
 import { CircularBuffer } from '../../lib/circular-buffer.cjs'
 import { WorkerTypes } from '../../lib/index.cjs'
 import {
@@ -29,10 +28,10 @@ describe('Pool utils test suite', () => {
     expect(getDefaultTasksQueueOptions(poolMaxSize)).toStrictEqual({
       concurrency: 1,
       size: Math.pow(poolMaxSize, 2),
-      taskStealing: true,
+      tasksFinishedTimeout: 2000,
       tasksStealingOnBackPressure: true,
       tasksStealingRatio: 0.6,
-      tasksFinishedTimeout: 2000,
+      taskStealing: true,
     })
   })
 
@@ -67,9 +66,9 @@ describe('Pool utils test suite', () => {
     )
     expect(measurementStatistics).toMatchObject({
       aggregate: 0.031,
+      average: 0.0010000000474974513,
       maximum: 0.02,
       minimum: 0.001,
-      average: 0.0010000000474974513,
     })
     updateMeasurementStatistics(
       measurementStatistics,
@@ -78,9 +77,9 @@ describe('Pool utils test suite', () => {
     )
     expect(measurementStatistics).toMatchObject({
       aggregate: 0.034,
+      average: 0.0020000000367872417,
       maximum: 0.02,
       minimum: 0.001,
-      average: 0.0020000000367872417,
     })
     updateMeasurementStatistics(
       measurementStatistics,
@@ -90,8 +89,8 @@ describe('Pool utils test suite', () => {
     expect(measurementStatistics).toMatchObject({
       aggregate: 0.04,
       maximum: 0.02,
-      minimum: 0.001,
       median: 0.003000000026077032,
+      minimum: 0.001,
     })
     updateMeasurementStatistics(
       measurementStatistics,
@@ -100,9 +99,9 @@ describe('Pool utils test suite', () => {
     )
     expect(measurementStatistics).toMatchObject({
       aggregate: 0.05,
+      average: 0.004999999975552782,
       maximum: 0.02,
       minimum: 0.001,
-      average: 0.004999999975552782,
     })
   })
 
index 69f1e15afc67c2760f71e24516202bbbb814195e..8698680f370dcddd803b9c157c7cae5f9a02f041 100644 (file)
@@ -1,8 +1,7 @@
+import { expect } from 'expect'
 import { Worker as ClusterWorker } from 'node:cluster'
 import { MessageChannel, Worker as ThreadWorker } from 'node:worker_threads'
 
-import { expect } from 'expect'
-
 import { CircularBuffer } from '../../lib/circular-buffer.cjs'
 import { WorkerTypes } from '../../lib/index.cjs'
 import { MeasurementHistorySize } from '../../lib/pools/worker.cjs'
@@ -11,7 +10,7 @@ import { PriorityQueue } from '../../lib/queues/priority-queue.cjs'
 import { DEFAULT_TASK_NAME } from '../../lib/utils.cjs'
 
 describe('Worker node test suite', () => {
-  let threadWorkerNode, clusterWorkerNode
+  let clusterWorkerNode, threadWorkerNode
 
   before('Create worker nodes', () => {
     threadWorkerNode = new WorkerNode(
@@ -236,40 +235,40 @@ describe('Worker node test suite', () => {
     expect(threadWorkerNode).toBeInstanceOf(WorkerNode)
     expect(threadWorkerNode.worker).toBeInstanceOf(ThreadWorker)
     expect(threadWorkerNode.info).toStrictEqual({
-      id: threadWorkerNode.worker.threadId,
-      type: WorkerTypes.thread,
+      backPressure: false,
+      backPressureStealing: false,
+      continuousStealing: false,
       dynamic: false,
+      id: threadWorkerNode.worker.threadId,
       ready: false,
       stealing: false,
       stolen: false,
-      continuousStealing: false,
-      backPressureStealing: false,
-      backPressure: false,
+      type: WorkerTypes.thread,
     })
     expect(threadWorkerNode.usage).toStrictEqual({
+      elu: {
+        active: {
+          history: expect.any(CircularBuffer),
+        },
+        idle: {
+          history: expect.any(CircularBuffer),
+        },
+      },
+      runTime: {
+        history: expect.any(CircularBuffer),
+      },
       tasks: {
         executed: 0,
         executing: 0,
-        queued: 0,
+        failed: 0,
         maxQueued: 0,
+        queued: 0,
         sequentiallyStolen: 0,
         stolen: 0,
-        failed: 0,
-      },
-      runTime: {
-        history: expect.any(CircularBuffer),
       },
       waitTime: {
         history: expect.any(CircularBuffer),
       },
-      elu: {
-        idle: {
-          history: expect.any(CircularBuffer),
-        },
-        active: {
-          history: expect.any(CircularBuffer),
-        },
-      },
     })
     expect(threadWorkerNode.usage.runTime.history.items.length).toBe(
       MeasurementHistorySize
@@ -298,40 +297,40 @@ describe('Worker node test suite', () => {
     expect(clusterWorkerNode).toBeInstanceOf(WorkerNode)
     expect(clusterWorkerNode.worker).toBeInstanceOf(ClusterWorker)
     expect(clusterWorkerNode.info).toStrictEqual({
-      id: clusterWorkerNode.worker.id,
-      type: WorkerTypes.cluster,
+      backPressure: false,
+      backPressureStealing: false,
+      continuousStealing: false,
       dynamic: false,
+      id: clusterWorkerNode.worker.id,
       ready: false,
       stealing: false,
       stolen: false,
-      continuousStealing: false,
-      backPressureStealing: false,
-      backPressure: false,
+      type: WorkerTypes.cluster,
     })
     expect(clusterWorkerNode.usage).toStrictEqual({
+      elu: {
+        active: {
+          history: expect.any(CircularBuffer),
+        },
+        idle: {
+          history: expect.any(CircularBuffer),
+        },
+      },
+      runTime: {
+        history: expect.any(CircularBuffer),
+      },
       tasks: {
         executed: 0,
         executing: 0,
-        queued: 0,
+        failed: 0,
         maxQueued: 0,
+        queued: 0,
         sequentiallyStolen: 0,
         stolen: 0,
-        failed: 0,
-      },
-      runTime: {
-        history: expect.any(CircularBuffer),
       },
       waitTime: {
         history: expect.any(CircularBuffer),
       },
-      elu: {
-        idle: {
-          history: expect.any(CircularBuffer),
-        },
-        active: {
-          history: expect.any(CircularBuffer),
-        },
-      },
     })
     expect(clusterWorkerNode.usage.runTime.history.items.length).toBe(
       MeasurementHistorySize
@@ -385,76 +384,76 @@ describe('Worker node test suite', () => {
     expect(
       threadWorkerNode.getTaskFunctionWorkerUsage(DEFAULT_TASK_NAME)
     ).toStrictEqual({
+      elu: {
+        active: {
+          history: expect.any(CircularBuffer),
+        },
+        idle: {
+          history: expect.any(CircularBuffer),
+        },
+      },
+      runTime: {
+        history: expect.any(CircularBuffer),
+      },
       tasks: {
         executed: 0,
         executing: 0,
+        failed: 0,
         queued: 0,
         sequentiallyStolen: 0,
         stolen: 0,
-        failed: 0,
-      },
-      runTime: {
-        history: expect.any(CircularBuffer),
       },
       waitTime: {
         history: expect.any(CircularBuffer),
       },
+    })
+    expect(threadWorkerNode.getTaskFunctionWorkerUsage('fn1')).toStrictEqual({
       elu: {
-        idle: {
+        active: {
           history: expect.any(CircularBuffer),
         },
-        active: {
+        idle: {
           history: expect.any(CircularBuffer),
         },
       },
-    })
-    expect(threadWorkerNode.getTaskFunctionWorkerUsage('fn1')).toStrictEqual({
+      runTime: {
+        history: expect.any(CircularBuffer),
+      },
       tasks: {
         executed: 0,
         executing: 0,
+        failed: 0,
         queued: 0,
         sequentiallyStolen: 0,
         stolen: 0,
-        failed: 0,
-      },
-      runTime: {
-        history: expect.any(CircularBuffer),
       },
       waitTime: {
         history: expect.any(CircularBuffer),
       },
+    })
+    expect(threadWorkerNode.getTaskFunctionWorkerUsage('fn2')).toStrictEqual({
       elu: {
-        idle: {
+        active: {
           history: expect.any(CircularBuffer),
         },
-        active: {
+        idle: {
           history: expect.any(CircularBuffer),
         },
       },
-    })
-    expect(threadWorkerNode.getTaskFunctionWorkerUsage('fn2')).toStrictEqual({
+      runTime: {
+        history: expect.any(CircularBuffer),
+      },
       tasks: {
         executed: 0,
         executing: 0,
+        failed: 0,
         queued: 0,
         sequentiallyStolen: 0,
         stolen: 0,
-        failed: 0,
-      },
-      runTime: {
-        history: expect.any(CircularBuffer),
       },
       waitTime: {
         history: expect.any(CircularBuffer),
       },
-      elu: {
-        idle: {
-          history: expect.any(CircularBuffer),
-        },
-        active: {
-          history: expect.any(CircularBuffer),
-        },
-      },
     })
     expect(threadWorkerNode.taskFunctionsUsage.size).toBe(2)
   })
index 707bb160efc40357561ecb54d5087abb7523a42b..1607b95867c3a2deac0cc9430520b0393d577e9d 100644 (file)
@@ -1,7 +1,7 @@
 const TaskFunctions = {
-  jsonIntegerSerialization: 'jsonIntegerSerialization',
-  fibonacci: 'fibonacci',
   factorial: 'factorial',
+  fibonacci: 'fibonacci',
+  jsonIntegerSerialization: 'jsonIntegerSerialization',
 }
 
 module.exports = { TaskFunctions }
index 5006df6e876065a75c893676185404d3c4c60a11..10fa7fa0de3f1493fcd50835a8d2387d16caa735 100644 (file)
@@ -98,12 +98,12 @@ const factorial = n => {
 
 const executeTaskFunction = data => {
   switch (data.function) {
-    case TaskFunctions.jsonIntegerSerialization:
-      return jsonIntegerSerialization(data.n || 100)
-    case TaskFunctions.fibonacci:
-      return fibonacci(data.n || 100)
     case TaskFunctions.factorial:
       return factorial(data.n || 100)
+    case TaskFunctions.fibonacci:
+      return fibonacci(data.n || 100)
+    case TaskFunctions.jsonIntegerSerialization:
+      return jsonIntegerSerialization(data.n || 100)
     default:
       throw new Error('Unknown worker function')
   }
index 25b4e5ae0cfd21cc84e5fb523748e84e44a95e0b..4f84d818cd703d4a6b64f8ac622be8534f6fe520 100644 (file)
@@ -1,8 +1,7 @@
+import { expect } from 'expect'
 import { randomInt } from 'node:crypto'
 import os from 'node:os'
 
-import { expect } from 'expect'
-
 import { KillBehaviors } from '../lib/index.cjs'
 import {
   availableParallelism,
@@ -182,12 +181,12 @@ describe('Utils test suite', () => {
     expect(isAsyncFunction(async function () {})).toBe(true)
     expect(isAsyncFunction(async function named () {})).toBe(true)
     class TestClass {
-      testSync () {}
-      async testAsync () {}
-      testArrowSync = () => {}
       testArrowAsync = async () => {}
-      static testStaticSync () {}
+      testArrowSync = () => {}
       static async testStaticAsync () {}
+      static testStaticSync () {}
+      async testAsync () {}
+      testSync () {}
     }
     const testClass = new TestClass()
     expect(isAsyncFunction(testClass.testSync)).toBe(false)
index 0f8286a53381938a0a3fbf06e336720b8b7bb5c3..6f1ebadc53dcb1e5141959ecaf088e34eb4b3d6a 100644 (file)
@@ -1,16 +1,16 @@
 'use strict'
 const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
 const {
-  jsonIntegerSerialization,
   factorial,
   fibonacci,
+  jsonIntegerSerialization,
 } = require('../../test-utils.cjs')
 
 module.exports = new ClusterWorker(
   {
-    jsonIntegerSerialization: data => jsonIntegerSerialization(data.n),
     factorial: data => factorial(data.n),
     fibonacci: data => fibonacci(data.n),
+    jsonIntegerSerialization: data => jsonIntegerSerialization(data.n),
   },
   {
     killBehavior: KillBehaviors.HARD,
index 9f4f4b4f4843573e53b992d3838728f5ba0fb0aa..265ea136c689820bb80c29f25f2981c725b03d2b 100644 (file)
@@ -1,5 +1,5 @@
 'use strict'
-const { KillBehaviors, ClusterWorker } = require('../../../lib/index.cjs')
+const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
 const {
   factorial,
   fibonacci,
@@ -8,11 +8,11 @@ const {
 
 module.exports = new ClusterWorker(
   {
+    factorial: { taskFunction: data => factorial(data.n) },
+    fibonacci: { priority: -5, taskFunction: data => fibonacci(data.n) },
     jsonIntegerSerialization: {
       taskFunction: data => jsonIntegerSerialization(data.n),
     },
-    factorial: { taskFunction: data => factorial(data.n) },
-    fibonacci: { taskFunction: data => fibonacci(data.n), priority: -5 },
   },
   {
     killBehavior: KillBehaviors.HARD,
index 384598448514ab22eb573853e5968063b7a4c7ba..5af94942bbfadac50145c551dcaaa1b8cd9c2f62 100644 (file)
@@ -1,7 +1,7 @@
 'use strict'
 const { ClusterWorker, KillBehaviors } = require('../../../lib/index.cjs')
-const { executeTaskFunction } = require('../../test-utils.cjs')
 const { TaskFunctions } = require('../../test-types.cjs')
+const { executeTaskFunction } = require('../../test-utils.cjs')
 
 /**
  *
index 7928ab02aa4e4b436a8a83cef1e4a4ac8b1287eb..e94097284be807ff504b9e555316c1d26d3afbc4 100644 (file)
@@ -7,9 +7,9 @@ import {
 
 export default new ThreadWorker(
   {
-    jsonIntegerSerialization: data => jsonIntegerSerialization(data.n),
     factorial: data => factorial(data.n),
     fibonacci: data => fibonacci(data.n),
+    jsonIntegerSerialization: data => jsonIntegerSerialization(data.n),
   },
   {
     killBehavior: KillBehaviors.HARD,
index 35cd6dafa570569713e20e9a487210d0e77a6906..566f436b269d31ce279acee3764250829c8ff1bb 100644 (file)
@@ -7,11 +7,11 @@ import {
 
 export default new ThreadWorker(
   {
+    factorial: { taskFunction: data => factorial(data.n) },
+    fibonacci: { priority: -5, taskFunction: data => fibonacci(data.n) },
     jsonIntegerSerialization: {
       taskFunction: data => jsonIntegerSerialization(data.n),
     },
-    factorial: { taskFunction: data => factorial(data.n) },
-    fibonacci: { taskFunction: data => fibonacci(data.n), priority: -5 },
   },
   {
     killBehavior: KillBehaviors.HARD,
index ed4fcbdb2f887068d53212b6e7e3b2e36aed5111..669f9b3fb272e3f7cd7cbd042a930e4ca42718e9 100644 (file)
@@ -25,8 +25,8 @@ describe('Abstract worker test suite', () => {
     const worker = new ThreadWorker(() => {})
     expect(worker.opts).toStrictEqual({
       killBehavior: KillBehaviors.SOFT,
-      maxInactiveTime: 60000,
       killHandler: EMPTY_FUNCTION,
+      maxInactiveTime: 60000,
     })
   })
 
@@ -70,13 +70,13 @@ describe('Abstract worker test suite', () => {
     }
     const worker = new ClusterWorker(() => {}, {
       killBehavior: KillBehaviors.HARD,
-      maxInactiveTime: 6000,
       killHandler,
+      maxInactiveTime: 6000,
     })
     expect(worker.opts).toStrictEqual({
       killBehavior: KillBehaviors.HARD,
-      maxInactiveTime: 6000,
       killHandler,
+      maxInactiveTime: 6000,
     })
   })
 
@@ -173,18 +173,18 @@ describe('Abstract worker test suite', () => {
       )
     )
     expect(
-      () => new ThreadWorker({ fn1: { taskFunction: fn1, priority: '' } })
+      () => new ThreadWorker({ fn1: { priority: '', taskFunction: fn1 } })
     ).toThrow(new TypeError("Invalid property 'priority': ''"))
     expect(
-      () => new ThreadWorker({ fn1: { taskFunction: fn1, priority: -21 } })
+      () => new ThreadWorker({ fn1: { priority: -21, taskFunction: fn1 } })
     ).toThrow(new RangeError("Property 'priority' must be between -20 and 19"))
     expect(
-      () => new ThreadWorker({ fn1: { taskFunction: fn1, priority: 20 } })
+      () => new ThreadWorker({ fn1: { priority: 20, taskFunction: fn1 } })
     ).toThrow(new RangeError("Property 'priority' must be between -20 and 19"))
     expect(
       () =>
         new ThreadWorker({
-          fn1: { taskFunction: fn1, strategy: 'invalidStrategy' },
+          fn1: { strategy: 'invalidStrategy', taskFunction: fn1 },
         })
     ).toThrow(new Error("Invalid worker choice strategy 'invalidStrategy'"))
   })
@@ -214,17 +214,17 @@ describe('Abstract worker test suite', () => {
 
   it('Verify that taskFunctions parameter with multiple task functions object is taken', () => {
     const fn1Obj = {
+      priority: 5,
       taskFunction: () => {
         return 1
       },
-      priority: 5,
     }
     const fn2Obj = {
+      priority: 6,
+      strategy: WorkerChoiceStrategies.LESS_BUSY,
       taskFunction: () => {
         return 2
       },
-      priority: 6,
-      strategy: WorkerChoiceStrategies.LESS_BUSY,
     }
     const worker = new ThreadWorker({
       fn1: fn1Obj,
@@ -264,12 +264,12 @@ describe('Abstract worker test suite', () => {
     }
     const worker = new ClusterWorker({ fn1, fn2 })
     expect(worker.hasTaskFunction(0)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is not a string'),
+      status: false,
     })
     expect(worker.hasTaskFunction('')).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is an empty string'),
+      status: false,
     })
     expect(worker.hasTaskFunction(DEFAULT_TASK_NAME)).toStrictEqual({
       status: true,
@@ -291,57 +291,57 @@ describe('Abstract worker test suite', () => {
     }
     const worker = new ThreadWorker(fn1)
     expect(worker.addTaskFunction(0, fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is not a string'),
+      status: false,
     })
     expect(worker.addTaskFunction('', fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is an empty string'),
+      status: false,
     })
     expect(worker.addTaskFunction('fn2', 0)).toStrictEqual({
-      status: false,
       error: new TypeError(
         "taskFunction object 'taskFunction' property 'undefined' is not a function"
       ),
+      status: false,
     })
     expect(worker.addTaskFunction('fn3', '')).toStrictEqual({
-      status: false,
       error: new TypeError(
         "taskFunction object 'taskFunction' property 'undefined' is not a function"
       ),
+      status: false,
     })
     expect(worker.addTaskFunction('fn2', { taskFunction: 0 })).toStrictEqual({
-      status: false,
       error: new TypeError(
         "taskFunction object 'taskFunction' property '0' is not a function"
       ),
+      status: false,
     })
     expect(worker.addTaskFunction('fn3', { taskFunction: '' })).toStrictEqual({
-      status: false,
       error: new TypeError(
         "taskFunction object 'taskFunction' property '' is not a function"
       ),
+      status: false,
     })
     expect(
-      worker.addTaskFunction('fn2', { taskFunction: () => {}, priority: -21 })
+      worker.addTaskFunction('fn2', { priority: -21, taskFunction: () => {} })
     ).toStrictEqual({
-      status: false,
       error: new RangeError("Property 'priority' must be between -20 and 19"),
+      status: false,
     })
     expect(
-      worker.addTaskFunction('fn3', { taskFunction: () => {}, priority: 20 })
+      worker.addTaskFunction('fn3', { priority: 20, taskFunction: () => {} })
     ).toStrictEqual({
-      status: false,
       error: new RangeError("Property 'priority' must be between -20 and 19"),
+      status: false,
     })
     expect(
       worker.addTaskFunction('fn2', {
-        taskFunction: () => {},
         strategy: 'invalidStrategy',
+        taskFunction: () => {},
       })
     ).toStrictEqual({
-      status: false,
       error: new Error("Invalid worker choice strategy 'invalidStrategy'"),
+      status: false,
     })
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
       taskFunction: expect.any(Function),
@@ -354,10 +354,10 @@ describe('Abstract worker test suite', () => {
       worker.taskFunctions.get('fn1')
     )
     expect(worker.addTaskFunction(DEFAULT_TASK_NAME, fn2)).toStrictEqual({
-      status: false,
       error: new Error(
         'Cannot add a task function with the default reserved name'
       ),
+      status: false,
     })
     worker.addTaskFunction('fn2', fn2)
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
@@ -413,12 +413,12 @@ describe('Abstract worker test suite', () => {
     }
     const worker = new ThreadWorker({ fn1, fn2 })
     expect(worker.setDefaultTaskFunction(0, fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is not a string'),
+      status: false,
     })
     expect(worker.setDefaultTaskFunction('', fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is an empty string'),
+      status: false,
     })
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
       taskFunction: expect.any(Function),
@@ -434,16 +434,16 @@ describe('Abstract worker test suite', () => {
       worker.taskFunctions.get('fn1')
     )
     expect(worker.setDefaultTaskFunction(DEFAULT_TASK_NAME)).toStrictEqual({
-      status: false,
       error: new Error(
         'Cannot set the default task function reserved name as the default task function'
       ),
+      status: false,
     })
     expect(worker.setDefaultTaskFunction('fn3')).toStrictEqual({
-      status: false,
       error: new Error(
         'Cannot set the default task function to a non-existing task function'
       ),
+      status: false,
     })
     worker.setDefaultTaskFunction('fn1')
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual(
index 2ad7911380c9844a12157e0c50f1232f9e991a27..fcb6564e83dbf832cb83f9ae9fc432040972afe6 100644 (file)
@@ -45,12 +45,12 @@ describe('Cluster worker test suite', () => {
       send: stub().returns(),
     })
     expect(worker.removeTaskFunction(0, fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is not a string'),
+      status: false,
     })
     expect(worker.removeTaskFunction('', fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is an empty string'),
+      status: false,
     })
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
       taskFunction: expect.any(Function),
@@ -66,16 +66,16 @@ describe('Cluster worker test suite', () => {
       worker.taskFunctions.get('fn1')
     )
     expect(worker.removeTaskFunction(DEFAULT_TASK_NAME)).toStrictEqual({
-      status: false,
       error: new Error(
         'Cannot remove the task function with the default reserved name'
       ),
+      status: false,
     })
     expect(worker.removeTaskFunction('fn1')).toStrictEqual({
-      status: false,
       error: new Error(
         'Cannot remove the task function used as the default task function'
       ),
+      status: false,
     })
     worker.removeTaskFunction('fn2')
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
index f78451f7d95482a33660076d71408c073661fc66..ed9f16ac318694c90b8074daef9caddc0af215d0 100644 (file)
@@ -23,9 +23,9 @@ describe('Thread worker test suite', () => {
     })
     worker.isMain = false
     worker.port = {
+      close: stub().returns(),
       postMessage: stub().returns(),
       unref: stub().returns(),
-      close: stub().returns(),
     }
     worker.handleKillMessage()
     expect(worker.port.postMessage.calledOnce).toBe(true)
@@ -46,12 +46,12 @@ describe('Thread worker test suite', () => {
       postMessage: stub().returns(),
     }
     expect(worker.removeTaskFunction(0, fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is not a string'),
+      status: false,
     })
     expect(worker.removeTaskFunction('', fn1)).toStrictEqual({
-      status: false,
       error: new TypeError('name parameter is an empty string'),
+      status: false,
     })
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
       taskFunction: expect.any(Function),
@@ -67,16 +67,16 @@ describe('Thread worker test suite', () => {
       worker.taskFunctions.get('fn1')
     )
     expect(worker.removeTaskFunction(DEFAULT_TASK_NAME)).toStrictEqual({
-      status: false,
       error: new Error(
         'Cannot remove the task function with the default reserved name'
       ),
+      status: false,
     })
     expect(worker.removeTaskFunction('fn1')).toStrictEqual({
-      status: false,
       error: new Error(
         'Cannot remove the task function used as the default task function'
       ),
+      status: false,
     })
     worker.removeTaskFunction('fn2')
     expect(worker.taskFunctions.get(DEFAULT_TASK_NAME)).toStrictEqual({
index d139d4e8102203dca02e0c06d47c499e54470fa7..328fb0c8e7fb6028d705b7ae406e13aaec164ff0 100644 (file)
@@ -24,8 +24,8 @@ try {
     )
   }
   rmSync(join(dirname(fileURLToPath(import.meta.url)), 'tmp'), {
-    recursive: true,
     force: true,
+    recursive: true,
   })
 } catch (e) {
   console.error(e)