chore(deps-dev): apply updates
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 20 Nov 2024 14:19:15 +0000 (15:19 +0100)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 20 Nov 2024 14:19:15 +0000 (15:19 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
51 files changed:
.lintstagedrc.js
benchmarks/worker-selection/least.mjs
benchmarks/worker-selection/round-robin.mjs
examples/typescript/http-client-pool/src/types.ts
examples/typescript/http-server-pool/express-cluster/src/worker.ts
examples/typescript/http-server-pool/express-hybrid/src/express-worker.ts
examples/typescript/http-server-pool/express-hybrid/src/request-handler-worker.ts
examples/typescript/http-server-pool/express-worker_threads/src/worker.ts
examples/typescript/http-server-pool/fastify-cluster/src/worker.ts
examples/typescript/http-server-pool/fastify-hybrid/src/fastify-worker.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/src/types.ts
examples/typescript/http-server-pool/fastify-worker_threads/src/worker.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/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/src/types.ts
examples/typescript/websocket-server-pool/ws-worker_threads/src/worker.ts
package.json
pnpm-lock.yaml
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/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/worker-node.ts
src/pools/worker.ts
src/queues/abstract-fixed-queue.ts
src/queues/priority-queue.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
tests/pools/worker-node.test.mjs
tests/utils.test.mjs

index c7d312894400942d300b26f0e2ca6ed39698bcfc..8e6fad9f5abcb9813bdb3e7758e8d485715dafe4 100644 (file)
@@ -1,8 +1,8 @@
 export default {
+  '**/*.json': ['biome format --no-errors-on-unmatched --write'],
   '**/*.{md,yml,yaml}': ['prettier --cache --write'],
   '**/*.{ts,tsx,js,jsx,cjs,mjs}': [
     'biome format --no-errors-on-unmatched --write',
     'eslint --cache --fix',
   ],
-  '**/*.json': ['biome format --no-errors-on-unmatched --write'],
 }
index 09679b0c1c0ae099272978ac16c9771612c5e22f..05e3dc6b1c4f7de4f43f398a27e778a260f626a8 100644 (file)
@@ -21,6 +21,23 @@ function generateRandomTasksMap (
 
 const tasksMap = generateRandomTasksMap(60, 20)
 
+/**
+ *
+ * @param tasksMap
+ * @returns
+ */
+function arraySortSelect (tasksMap) {
+  const tasksArray = Array.from(tasksMap)
+  return tasksArray.sort((a, b) => {
+    if (a[1] < b[1]) {
+      return -1
+    } else if (a[1] > b[1]) {
+      return 1
+    }
+    return 0
+  })[0]
+}
+
 /**
  *
  * @param tasksMap
@@ -40,23 +57,6 @@ function loopSelect (tasksMap) {
   return [minKey, minValue]
 }
 
-/**
- *
- * @param tasksMap
- * @returns
- */
-function arraySortSelect (tasksMap) {
-  const tasksArray = Array.from(tasksMap)
-  return tasksArray.sort((a, b) => {
-    if (a[1] < b[1]) {
-      return -1
-    } else if (a[1] > b[1]) {
-      return 1
-    }
-    return 0
-  })[0]
-}
-
 const defaultComparator = (a, b) => {
   return a < b
 }
@@ -69,18 +69,6 @@ const randomPivotIndexSelect = (leftIndex, rightIndex) => {
   return randomInt(leftIndex, rightIndex)
 }
 
-/**
- *
- * @param array
- * @param index1
- * @param index2
- */
-function swap (array, index1, index2) {
-  const tmp = array[index1]
-  array[index1] = array[index2]
-  array[index2] = tmp
-}
-
 /**
  *
  * @param array
@@ -110,6 +98,72 @@ function partition (
   return storeIndex
 }
 
+/**
+ *
+ * @param tasksMap
+ * @returns
+ */
+function quickSelectLoop (tasksMap) {
+  const tasksArray = Array.from(tasksMap)
+
+  return selectLoop(tasksArray, 0, 0, tasksArray.length - 1, (a, b) => {
+    return a[1] < b[1]
+  })
+}
+
+/**
+ *
+ * @param tasksMap
+ * @returns
+ */
+function quickSelectLoopRandomPivot (tasksMap) {
+  const tasksArray = Array.from(tasksMap)
+
+  return selectLoop(
+    tasksArray,
+    0,
+    0,
+    tasksArray.length - 1,
+    (a, b) => {
+      return a[1] < b[1]
+    },
+    randomPivotIndexSelect
+  )
+}
+
+/**
+ *
+ * @param tasksMap
+ * @returns
+ */
+function quickSelectRecursion (tasksMap) {
+  const tasksArray = Array.from(tasksMap)
+
+  return selectRecursion(tasksArray, 0, 0, tasksArray.length - 1, (a, b) => {
+    return a[1] < b[1]
+  })
+}
+
+/**
+ *
+ * @param tasksMap
+ * @returns
+ */
+function quickSelectRecursionRandomPivot (tasksMap) {
+  const tasksArray = Array.from(tasksMap)
+
+  return selectRecursion(
+    tasksArray,
+    0,
+    0,
+    tasksArray.length - 1,
+    (a, b) => {
+      return a[1] < b[1]
+    },
+    randomPivotIndexSelect
+  )
+}
+
 /**
  *
  * @param array
@@ -174,68 +228,14 @@ function selectRecursion (
 
 /**
  *
- * @param tasksMap
- * @returns
- */
-function quickSelectLoop (tasksMap) {
-  const tasksArray = Array.from(tasksMap)
-
-  return selectLoop(tasksArray, 0, 0, tasksArray.length - 1, (a, b) => {
-    return a[1] < b[1]
-  })
-}
-
-/**
- *
- * @param tasksMap
- * @returns
- */
-function quickSelectLoopRandomPivot (tasksMap) {
-  const tasksArray = Array.from(tasksMap)
-
-  return selectLoop(
-    tasksArray,
-    0,
-    0,
-    tasksArray.length - 1,
-    (a, b) => {
-      return a[1] < b[1]
-    },
-    randomPivotIndexSelect
-  )
-}
-
-/**
- *
- * @param tasksMap
- * @returns
- */
-function quickSelectRecursion (tasksMap) {
-  const tasksArray = Array.from(tasksMap)
-
-  return selectRecursion(tasksArray, 0, 0, tasksArray.length - 1, (a, b) => {
-    return a[1] < b[1]
-  })
-}
-
-/**
- *
- * @param tasksMap
- * @returns
+ * @param array
+ * @param index1
+ * @param index2
  */
-function quickSelectRecursionRandomPivot (tasksMap) {
-  const tasksArray = Array.from(tasksMap)
-
-  return selectRecursion(
-    tasksArray,
-    0,
-    0,
-    tasksArray.length - 1,
-    (a, b) => {
-      return a[1] < b[1]
-    },
-    randomPivotIndexSelect
-  )
+function swap (array, index1, index2) {
+  const tmp = array[index1]
+  array[index1] = array[index2]
+  array[index2] = tmp
 }
 
 group('Least used worker tasks distribution', () => {
index 64ea78da719cf0ef0607bfa4f8e27cb0e4977388..4b35793197ceba557d7180b04af18f51fb139b44 100644 (file)
@@ -13,6 +13,16 @@ const workers = generateWorkersArray(60)
 
 let nextWorkerIndex
 
+/**
+ * @returns
+ */
+function roundRobinIncrementModulo () {
+  const chosenWorker = workers[nextWorkerIndex]
+  nextWorkerIndex++
+  nextWorkerIndex %= workers.length
+  return chosenWorker
+}
+
 /**
  * @returns
  */
@@ -43,16 +53,6 @@ function roundRobinTernaryWithPreChoosing () {
   return chosenWorker
 }
 
-/**
- * @returns
- */
-function roundRobinIncrementModulo () {
-  const chosenWorker = workers[nextWorkerIndex]
-  nextWorkerIndex++
-  nextWorkerIndex %= workers.length
-  return chosenWorker
-}
-
 group('Round robin tasks distribution', () => {
   bench('Ternary off by one', () => {
     nextWorkerIndex = 0
index 36df0cd35ae5e540612a800c1de7fdea549c0cfb..83eaed0fcb80b5b24f6b9a58ca9dfe4ae88f5f3d 100644 (file)
@@ -1,9 +1,9 @@
 import type { AxiosRequestConfig } from 'axios'
-import type { URL } from 'node:url'
 import type {
   RequestInfo as NodeFetchRequestInfo,
   RequestInit as NodeFetchRequestInit,
 } from 'node-fetch'
+import type { URL } from 'node:url'
 
 export interface WorkerData {
   axiosRequestConfig?: AxiosRequestConfig
index 35a08240094f1f54d42833a7b581bd3b4d352caf..5f70b1da693d0cfce09f0a390cce55767370c583 100644 (file)
@@ -7,6 +7,16 @@ import { ClusterWorker } from 'poolifier'
 import type { WorkerData, WorkerResponse } from './types.js'
 
 class ExpressWorker extends ClusterWorker<WorkerData, WorkerResponse> {
+  private static server: Server
+
+  public constructor () {
+    super(ExpressWorker.startExpress, {
+      killHandler: () => {
+        ExpressWorker.server.close()
+      },
+    })
+  }
+
   private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
@@ -20,8 +30,6 @@ class ExpressWorker extends ClusterWorker<WorkerData, WorkerResponse> {
     }
   }
 
-  private static server: Server
-
   private static readonly startExpress = (
     workerData?: WorkerData
   ): WorkerResponse => {
@@ -58,14 +66,6 @@ class ExpressWorker extends ClusterWorker<WorkerData, WorkerResponse> {
       status: true,
     }
   }
-
-  public constructor () {
-    super(ExpressWorker.startExpress, {
-      killHandler: () => {
-        ExpressWorker.server.close()
-      },
-    })
-  }
 }
 
 export const expressWorker = new ExpressWorker()
index 053a25ab47f0678901ef420aeb3892daf79b440d..93f5ed948c62e4f6941f2c2fd532403f45df0c1e 100644 (file)
@@ -31,6 +31,15 @@ class ExpressWorker extends ClusterWorker<
 
   private static server: Server
 
+  public constructor () {
+    super(ExpressWorker.startExpress, {
+      killHandler: async () => {
+        await ExpressWorker.requestHandlerPool.destroy()
+        ExpressWorker.server.close()
+      },
+    })
+  }
+
   private static readonly startExpress = (
     workerData?: ClusterWorkerData
   ): ClusterWorkerResponse => {
@@ -84,15 +93,6 @@ class ExpressWorker extends ClusterWorker<
       status: true,
     }
   }
-
-  public constructor () {
-    super(ExpressWorker.startExpress, {
-      killHandler: async () => {
-        await ExpressWorker.requestHandlerPool.destroy()
-        ExpressWorker.server.close()
-      },
-    })
-  }
 }
 
 export const expressWorker = new ExpressWorker()
index aac0d8d08f57e27a1716d3696cb25d833ecc1725..5edaddad78564be199f1e940eda80ff93af21b77 100644 (file)
@@ -10,19 +10,6 @@ class RequestHandlerWorker<
   Data extends ThreadWorkerData<DataPayload>,
   Response extends ThreadWorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: bigint | number): bigint => {
-    if (n === 0 || n === 1) {
-      return 1n
-    } else {
-      n = BigInt(n)
-      let factorial = 1n
-      for (let i = 1n; i <= n; i++) {
-        factorial *= i
-      }
-      return factorial
-    }
-  }
-
   public constructor () {
     super({
       echo: (workerData?: Data) => {
@@ -40,6 +27,19 @@ class RequestHandlerWorker<
       },
     })
   }
+
+  private static readonly factorial = (n: bigint | number): bigint => {
+    if (n === 0 || n === 1) {
+      return 1n
+    } else {
+      n = BigInt(n)
+      let factorial = 1n
+      for (let i = 1n; i <= n; i++) {
+        factorial *= i
+      }
+      return factorial
+    }
+  }
 }
 
 export const requestHandlerWorker = new RequestHandlerWorker<
index 3e8699bc639a251e88f3d665be6962ca2e2258ce..9796ccb7ef7bae5ec398af8b9695d65a3deaf8d3 100644 (file)
@@ -6,19 +6,6 @@ class RequestHandlerWorker<
   Data extends WorkerData<BodyPayload>,
   Response extends WorkerResponse<BodyPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial: (n: bigint | number) => bigint = n => {
-    if (n === 0 || n === 1) {
-      return 1n
-    } else {
-      n = BigInt(n)
-      let factorial = 1n
-      for (let i = 1n; i <= n; i++) {
-        factorial *= i
-      }
-      return factorial
-    }
-  }
-
   public constructor () {
     super({
       echo: (workerData?: Data) => {
@@ -36,6 +23,19 @@ class RequestHandlerWorker<
       },
     })
   }
+
+  private static readonly factorial: (n: bigint | number) => bigint = n => {
+    if (n === 0 || n === 1) {
+      return 1n
+    } else {
+      n = BigInt(n)
+      let factorial = 1n
+      for (let i = 1n; i <= n; i++) {
+        factorial *= i
+      }
+      return factorial
+    }
+  }
 }
 
 export const requestHandlerWorker = new RequestHandlerWorker<
index c723e207fb2bf7c2d4f78e1858a18587d4900404..7ef976370ccd3fc3955b2fc30519492d445774df 100644 (file)
@@ -6,6 +6,16 @@ import { ClusterWorker } from 'poolifier'
 import type { WorkerData, WorkerResponse } from './types.js'
 
 class FastifyWorker extends ClusterWorker<WorkerData, WorkerResponse> {
+  private static fastify: FastifyInstance
+
+  public constructor () {
+    super(FastifyWorker.startFastify, {
+      killHandler: async () => {
+        await FastifyWorker.fastify.close()
+      },
+    })
+  }
+
   private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
@@ -19,8 +29,6 @@ class FastifyWorker extends ClusterWorker<WorkerData, WorkerResponse> {
     }
   }
 
-  private static fastify: FastifyInstance
-
   private static readonly startFastify = async (
     workerData?: WorkerData
   ): Promise<WorkerResponse> => {
@@ -48,14 +56,6 @@ class FastifyWorker extends ClusterWorker<WorkerData, WorkerResponse> {
       status: true,
     }
   }
-
-  public constructor () {
-    super(FastifyWorker.startFastify, {
-      killHandler: async () => {
-        await FastifyWorker.fastify.close()
-      },
-    })
-  }
 }
 
 export const fastifyWorker = new FastifyWorker()
index 6b10dbd911a654509afe8874042b2e55e10bd2cb..a93e229bb5c5451da40593d3d53f59afc2ccd27b 100644 (file)
@@ -13,6 +13,15 @@ class FastifyWorker extends ClusterWorker<
 > {
   private static fastify: FastifyInstance
 
+  public constructor () {
+    super(FastifyWorker.startFastify, {
+      killHandler: async () => {
+        await FastifyWorker.fastify.pool.destroy()
+        await FastifyWorker.fastify.close()
+      },
+    })
+  }
+
   private static readonly startFastify = async (
     workerData?: ClusterWorkerData
   ): Promise<ClusterWorkerResponse> => {
@@ -49,15 +58,6 @@ class FastifyWorker extends ClusterWorker<
       status: true,
     }
   }
-
-  public constructor () {
-    super(FastifyWorker.startFastify, {
-      killHandler: async () => {
-        await FastifyWorker.fastify.pool.destroy()
-        await FastifyWorker.fastify.close()
-      },
-    })
-  }
 }
 
 export const fastifyWorker = new FastifyWorker()
index aac0d8d08f57e27a1716d3696cb25d833ecc1725..5edaddad78564be199f1e940eda80ff93af21b77 100644 (file)
@@ -10,19 +10,6 @@ class RequestHandlerWorker<
   Data extends ThreadWorkerData<DataPayload>,
   Response extends ThreadWorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: bigint | number): bigint => {
-    if (n === 0 || n === 1) {
-      return 1n
-    } else {
-      n = BigInt(n)
-      let factorial = 1n
-      for (let i = 1n; i <= n; i++) {
-        factorial *= i
-      }
-      return factorial
-    }
-  }
-
   public constructor () {
     super({
       echo: (workerData?: Data) => {
@@ -40,6 +27,19 @@ class RequestHandlerWorker<
       },
     })
   }
+
+  private static readonly factorial = (n: bigint | number): bigint => {
+    if (n === 0 || n === 1) {
+      return 1n
+    } else {
+      n = BigInt(n)
+      let factorial = 1n
+      for (let i = 1n; i <= n; i++) {
+        factorial *= i
+      }
+      return factorial
+    }
+  }
 }
 
 export const requestHandlerWorker = new RequestHandlerWorker<
index b1a6971b3b2676419f517994d1c21b81c5704f80..28ddd6ac60caf9bd976f31433f92d09693385c51 100644 (file)
@@ -13,6 +13,12 @@ export interface DataPayload {
   number?: number
 }
 
+export interface FastifyPoolifierOptions extends ThreadPoolOptions {
+  maxWorkers?: number
+  minWorkers?: number
+  workerFile: string
+}
+
 export interface ThreadWorkerData<T = unknown> {
   data: T
 }
@@ -20,9 +26,3 @@ export interface ThreadWorkerData<T = unknown> {
 export interface ThreadWorkerResponse<T = unknown> {
   data: T
 }
-
-export interface FastifyPoolifierOptions extends ThreadPoolOptions {
-  maxWorkers?: number
-  minWorkers?: number
-  workerFile: string
-}
index 255a0fcb1cea91d0131e3eee3024b807d8de25df..6a61b3fec740d544bed7768c11bcbd3da46cecba 100644 (file)
@@ -4,6 +4,12 @@ export interface BodyPayload {
   number?: number
 }
 
+export interface FastifyPoolifierOptions extends ThreadPoolOptions {
+  maxWorkers?: number
+  minWorkers?: number
+  workerFile: string
+}
+
 export interface WorkerData<T = unknown> {
   body: T
 }
@@ -11,9 +17,3 @@ export interface WorkerData<T = unknown> {
 export interface WorkerResponse<T = unknown> {
   body: T
 }
-
-export interface FastifyPoolifierOptions extends ThreadPoolOptions {
-  maxWorkers?: number
-  minWorkers?: number
-  workerFile: string
-}
index 3e8699bc639a251e88f3d665be6962ca2e2258ce..9796ccb7ef7bae5ec398af8b9695d65a3deaf8d3 100644 (file)
@@ -6,19 +6,6 @@ class RequestHandlerWorker<
   Data extends WorkerData<BodyPayload>,
   Response extends WorkerResponse<BodyPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial: (n: bigint | number) => bigint = n => {
-    if (n === 0 || n === 1) {
-      return 1n
-    } else {
-      n = BigInt(n)
-      let factorial = 1n
-      for (let i = 1n; i <= n; i++) {
-        factorial *= i
-      }
-      return factorial
-    }
-  }
-
   public constructor () {
     super({
       echo: (workerData?: Data) => {
@@ -36,6 +23,19 @@ class RequestHandlerWorker<
       },
     })
   }
+
+  private static readonly factorial: (n: bigint | number) => bigint = n => {
+    if (n === 0 || n === 1) {
+      return 1n
+    } else {
+      n = BigInt(n)
+      let factorial = 1n
+      for (let i = 1n; i <= n; i++) {
+        factorial *= i
+      }
+      return factorial
+    }
+  }
 }
 
 export const requestHandlerWorker = new RequestHandlerWorker<
index 21c37e3794ccd8a630cb354b57de67c58918b10d..2149ec47b1e018a51d5f1f3606a72e6eeb2503af 100644 (file)
@@ -3,15 +3,15 @@ export enum MessageType {
   factorial = 'factorial',
 }
 
+export interface DataPayload {
+  number?: number
+}
+
 export interface MessagePayload<T = unknown> {
   data: T
   type: MessageType
 }
 
-export interface DataPayload {
-  number?: number
-}
-
 export interface WorkerData {
   port: number
 }
index a5577d7ae57e26d7977737c98a90bbbbeebb0891..5b44a950549356594971f4e4c795ac34305a3756 100644 (file)
@@ -10,6 +10,16 @@ import {
 } from './types.js'
 
 class WebSocketServerWorker extends ClusterWorker<WorkerData, WorkerResponse> {
+  private static wss: WebSocketServer
+
+  public constructor () {
+    super(WebSocketServerWorker.startWebSocketServer, {
+      killHandler: () => {
+        WebSocketServerWorker.wss.close()
+      },
+    })
+  }
+
   private static readonly factorial = (n: bigint | number): bigint => {
     if (n === 0 || n === 1) {
       return 1n
@@ -39,6 +49,7 @@ class WebSocketServerWorker extends ClusterWorker<WorkerData, WorkerResponse> {
       ws.on('error', console.error)
       ws.on('message', (message: RawData) => {
         const { data, type } = JSON.parse(
+          // eslint-disable-next-line @typescript-eslint/no-base-to-string
           message.toString()
         ) as MessagePayload<DataPayload>
         switch (type) {
@@ -72,16 +83,6 @@ class WebSocketServerWorker extends ClusterWorker<WorkerData, WorkerResponse> {
       status: true,
     }
   }
-
-  private static wss: WebSocketServer
-
-  public constructor () {
-    super(WebSocketServerWorker.startWebSocketServer, {
-      killHandler: () => {
-        WebSocketServerWorker.wss.close()
-      },
-    })
-  }
 }
 
 export const webSocketServerWorker = new WebSocketServerWorker()
index 539a03ff478d2a6bd8a2b9a8eb6cd13bd349b0c7..89f53f9d36b2f6363686c321692f510a9a76d50c 100644 (file)
@@ -10,19 +10,6 @@ class RequestHandlerWorker<
   Data extends ThreadWorkerData<DataPayload>,
   Response extends ThreadWorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: bigint | number): bigint => {
-    if (n === 0 || n === 1) {
-      return 1n
-    } else {
-      n = BigInt(n)
-      let factorial = 1n
-      for (let i = 1n; i <= n; i++) {
-        factorial *= i
-      }
-      return factorial
-    }
-  }
-
   public constructor () {
     super({
       echo: (workerData?: Data) => {
@@ -38,6 +25,19 @@ class RequestHandlerWorker<
       },
     })
   }
+
+  private static readonly factorial = (n: bigint | number): bigint => {
+    if (n === 0 || n === 1) {
+      return 1n
+    } else {
+      n = BigInt(n)
+      let factorial = 1n
+      for (let i = 1n; i <= n; i++) {
+        factorial *= i
+      }
+      return factorial
+    }
+  }
 }
 
 export const requestHandlerWorker = new RequestHandlerWorker<
index 8bc8a6676ddba897c1e0666330de56f636cc6e26..843536b88f45a0d2966008feafcbc0c69bb780b1 100644 (file)
@@ -5,15 +5,6 @@ export enum MessageType {
   factorial = 'factorial',
 }
 
-export interface MessagePayload<T = unknown> {
-  data: T
-  type: MessageType
-}
-
-export interface DataPayload {
-  number?: number
-}
-
 export interface ClusterWorkerData extends ThreadPoolOptions {
   maxWorkers?: number
   minWorkers?: number
@@ -26,6 +17,15 @@ export interface ClusterWorkerResponse {
   status: boolean
 }
 
+export interface DataPayload {
+  number?: number
+}
+
+export interface MessagePayload<T = unknown> {
+  data: T
+  type: MessageType
+}
+
 export interface ThreadWorkerData<T = unknown> {
   data: T
 }
index 68cbdf2aaae38a1cc079f1605175874f45064f25..d5695575c15ac1596edcbca535e74957408e81e5 100644 (file)
@@ -28,6 +28,17 @@ class WebSocketServerWorker extends ClusterWorker<
     ThreadWorkerResponse<DataPayload>
   >
 
+  private static wss: WebSocketServer
+
+  public constructor () {
+    super(WebSocketServerWorker.startWebSocketServer, {
+      killHandler: async () => {
+        await WebSocketServerWorker.requestHandlerPool.destroy()
+        WebSocketServerWorker.wss.close()
+      },
+    })
+  }
+
   private static readonly startWebSocketServer = (
     workerData?: ClusterWorkerData
   ): ClusterWorkerResponse => {
@@ -55,6 +66,7 @@ class WebSocketServerWorker extends ClusterWorker<
       ws.on('error', console.error)
       ws.on('message', (message: RawData) => {
         const { data, type } = JSON.parse(
+          // eslint-disable-next-line @typescript-eslint/no-base-to-string
           message.toString()
         ) as MessagePayload<DataPayload>
         switch (type) {
@@ -98,17 +110,6 @@ class WebSocketServerWorker extends ClusterWorker<
       status: true,
     }
   }
-
-  private static wss: WebSocketServer
-
-  public constructor () {
-    super(WebSocketServerWorker.startWebSocketServer, {
-      killHandler: async () => {
-        await WebSocketServerWorker.requestHandlerPool.destroy()
-        WebSocketServerWorker.wss.close()
-      },
-    })
-  }
 }
 
 export const webSocketServerWorker = new WebSocketServerWorker()
index b16cd4a44ae84c2de3733129ca4fa22ad715c287..95499a326268fdccd1f06fb2b663dd35cd6b087d 100644 (file)
@@ -3,15 +3,15 @@ export enum MessageType {
   factorial = 'factorial',
 }
 
+export interface DataPayload {
+  number?: number
+}
+
 export interface MessagePayload<T = unknown> {
   data: T
   type: MessageType
 }
 
-export interface DataPayload {
-  number?: number
-}
-
 export interface WorkerData<T = unknown> {
   data: T
 }
index 575066905615f7db485e547a99b5523608dc1d02..9bc8ed659730dbbb4c26c46e925f9c9d5074ee49 100644 (file)
@@ -6,19 +6,6 @@ class RequestHandlerWorker<
   Data extends WorkerData<DataPayload>,
   Response extends WorkerResponse<DataPayload>
 > extends ThreadWorker<Data, Response> {
-  private static readonly factorial = (n: bigint | number): bigint => {
-    if (n === 0 || n === 1) {
-      return 1n
-    } else {
-      n = BigInt(n)
-      let factorial = 1n
-      for (let i = 1n; i <= n; i++) {
-        factorial *= i
-      }
-      return factorial
-    }
-  }
-
   public constructor () {
     super({
       echo: (workerData?: Data) => {
@@ -34,6 +21,19 @@ class RequestHandlerWorker<
       },
     })
   }
+
+  private static readonly factorial = (n: bigint | number): bigint => {
+    if (n === 0 || n === 1) {
+      return 1n
+    } else {
+      n = BigInt(n)
+      let factorial = 1n
+      for (let i = 1n; i <= n; i++) {
+        factorial *= i
+      }
+      return factorial
+    }
+  }
 }
 
 export const requestHandlerWorker = new RequestHandlerWorker<
index b0d25cfbf41c958e4ec89aedebfb76ee7f2d51b6..b755d1c707e5691a6ab195f7c1a9f0bd7a96d698 100644 (file)
     "eslint": "^9.15.0",
     "eslint-define-config": "^2.1.0",
     "eslint-plugin-jsdoc": "^50.5.0",
-    "eslint-plugin-perfectionist": "^3.9.1",
+    "eslint-plugin-perfectionist": "^4.0.3",
     "globals": "^15.12.0",
     "husky": "^9.1.7",
     "lint-staged": "^15.2.10",
index 9d61bdaa6f614569c77f22baaa8de49305d5415b..2731ba0ac5a7069a0f4ad4149802c8cbcdf38fc2 100644 (file)
@@ -57,8 +57,8 @@ importers:
         specifier: ^50.5.0
         version: 50.5.0(eslint@9.15.0(jiti@1.21.6))
       eslint-plugin-perfectionist:
-        specifier: ^3.9.1
-        version: 3.9.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
+        specifier: ^4.0.3
+        version: 4.0.3(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       globals:
         specifier: ^15.12.0
         version: 15.12.0
@@ -726,8 +726,8 @@ packages:
   '@sinonjs/text-encoding@0.7.3':
     resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==}
 
-  '@stylistic/eslint-plugin@2.10.1':
-    resolution: {integrity: sha512-U+4yzNXElTf9q0kEfnloI9XbOyD4cnEQCxjUI94q0+W++0GAEQvJ/slwEj9lwjDHfGADRSr+Tco/z0XJvmDfCQ==}
+  '@stylistic/eslint-plugin@2.11.0':
+    resolution: {integrity: sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: '>=8.40.0'
@@ -1315,24 +1315,11 @@ packages:
     peerDependencies:
       eslint: '>=8.23.0'
 
-  eslint-plugin-perfectionist@3.9.1:
-    resolution: {integrity: sha512-9WRzf6XaAxF4Oi5t/3TqKP5zUjERhasHmLFHin2Yw6ZAp/EP/EVA2dr3BhQrrHWCm5SzTMZf0FcjDnBkO2xFkA==}
+  eslint-plugin-perfectionist@4.0.3:
+    resolution: {integrity: sha512-CyafnreF6boy4lf1XaF72U8NbkwrfjU/mOf1y6doaDMS9zGXhUU1DSk+ZPf/rVwCf1PL1m+rhHqFs+IcB8kDmA==}
     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.1
-      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==}
@@ -2056,12 +2043,13 @@ 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==}
 
+  natural-orderby@5.0.0:
+    resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==}
+    engines: {node: '>=18'}
+
   neostandard@0.11.8:
     resolution: {integrity: sha512-gKgvK0EDlA0x7F1hMu/UdPCRtSSG4d9CBxVym4PMWoHz8+a1ffEz2fPU5FMH3RUqzL1ACvroUMiywMGWZSY+Mw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2730,8 +2718,8 @@ packages:
     engines: {node: '>= 14'}
     hasBin: true
 
-  yaml@2.6.0:
-    resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==}
+  yaml@2.6.1:
+    resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==}
     engines: {node: '>= 14'}
     hasBin: true
 
@@ -3371,7 +3359,7 @@ snapshots:
 
   '@sinonjs/text-encoding@0.7.3': {}
 
-  '@stylistic/eslint-plugin@2.10.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
+  '@stylistic/eslint-plugin@2.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)':
     dependencies:
       '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       eslint: 9.15.0(jiti@1.21.6)
@@ -3806,7 +3794,7 @@ snapshots:
     dependencies:
       '@cspell/cspell-types': 8.16.0
       comment-json: 4.2.5
-      yaml: 2.6.0
+      yaml: 2.6.1
 
   cspell-dictionary@8.16.0:
     dependencies:
@@ -4111,13 +4099,12 @@ snapshots:
       minimatch: 9.0.5
       semver: 7.6.3
 
-  eslint-plugin-perfectionist@3.9.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
+  eslint-plugin-perfectionist@4.0.3(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
     dependencies:
       '@typescript-eslint/types': 8.15.0
       '@typescript-eslint/utils': 8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       eslint: 9.15.0(jiti@1.21.6)
-      minimatch: 9.0.5
-      natural-compare-lite: 1.4.0
+      natural-orderby: 5.0.0
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -4914,14 +4901,14 @@ snapshots:
 
   ms@2.1.3: {}
 
-  natural-compare-lite@1.4.0: {}
-
   natural-compare@1.4.0: {}
 
+  natural-orderby@5.0.0: {}
+
   neostandard@0.11.8(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
     dependencies:
       '@humanwhocodes/gitignore-to-minimatch': 1.0.2
-      '@stylistic/eslint-plugin': 2.10.1(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
+      '@stylistic/eslint-plugin': 2.11.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3)
       eslint: 9.15.0(jiti@1.21.6)
       eslint-plugin-n: 17.13.2(eslint@9.15.0(jiti@1.21.6))
       eslint-plugin-promise: 7.1.0(eslint@9.15.0(jiti@1.21.6))
@@ -5509,7 +5496,7 @@ snapshots:
       minimatch: 9.0.5
       shiki: 1.23.1
       typescript: 5.6.3
-      yaml: 2.6.0
+      yaml: 2.6.1
 
   typescript-eslint@8.15.0(eslint@9.15.0(jiti@1.21.6))(typescript@5.6.3):
     dependencies:
@@ -5662,7 +5649,7 @@ snapshots:
 
   yaml@2.5.1: {}
 
-  yaml@2.6.0: {}
+  yaml@2.6.1: {}
 
   yargs-parser@20.2.9: {}
 
index 803af1294f83eb0f094cd6d2ab40e52353408ed7..bcd5920a93991508748ddee10edbd9fe4cf34dc4 100644 (file)
@@ -8,11 +8,11 @@ export const defaultBufferSize = 2048
  * @internal
  */
 export class CircularBuffer {
+  public size: number
   private readonly items: Float32Array
   private readonly maxArrayIdx: number
   private readIdx: number
   private writeIdx: number
-  public size: number
 
   /**
    * @param size - Buffer size. @defaultValue defaultBufferSize
@@ -27,23 +27,6 @@ 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.
@@ -94,4 +77,21 @@ export class CircularBuffer {
   public toArray (): number[] {
     return Array.from(this.items.filter(item => item !== -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`
+      )
+    }
+  }
 }
index 6b551d70e352efc8a74764fd6f598155e04228dd..f3f1d8491e085756fcd48f27bcb1cac34f4eafb2 100644 (file)
@@ -82,2307 +82,2306 @@ export abstract class AbstractPool<
   Data = unknown,
   Response = unknown
 > implements IPool<Worker, Data, Response> {
-  /**
-   * The task execution response promise map:
-   * - `key`: The message id of each submitted task.
-   * - `value`: An object that contains task's worker node key, execution response promise resolve and reject callbacks, async resource.
-   *
-   * When we receive a message from the worker, we get a map entry with the promise resolve/reject bound to the message id.
-   */
-  protected promiseResponseMap: Map<
-    `${string}-${string}-${string}-${string}-${string}`,
-    PromiseResponseWrapper<Response>
-  > = new Map<
-    `${string}-${string}-${string}-${string}-${string}`,
-    PromiseResponseWrapper<Response>
-  >()
-
-  /**
-   * Worker choice strategies context referencing worker choice algorithms implementation.
-   */
-  protected workerChoiceStrategiesContext?: WorkerChoiceStrategiesContext<
-    Worker,
-    Data,
-    Response
-  >
-
-  /**
-   * 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 { 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 back pressure event has been emitted or not.
-   */
-  private backPressureEventEmitted: boolean
-
-  /**
-   * Whether the pool busy event has been emitted or not.
-   */
-  private busyEventEmitted: boolean
-
-  /**
-   * Whether the pool is destroying or not.
-   */
-  private destroying: boolean
-
-  /**
-   * 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
-  ): 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
-  }
+  /** @inheritDoc */
+  public emitter?: EventEmitterAsyncResource
+  /** @inheritDoc */
+  public readonly workerNodes: IWorkerNode<Worker, Data>[] = []
 
-  /**
-   * 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: undefined | WorkerChoiceStrategy) => strategy != null
+  /** @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
+      ),
+      executedTasks: this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          accumulator + workerNode.usage.tasks.executed,
+        0
+      ),
+      executingTasks: this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          accumulator + workerNode.usage.tasks.executing,
+        0
+      ),
+      failedTasks: this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          accumulator + workerNode.usage.tasks.failed,
+        0
+      ),
+      idleWorkerNodes: this.workerNodes.reduce(
+        (accumulator, _, workerNodeKey) =>
+          this.isWorkerNodeIdle(workerNodeKey) ? accumulator + 1 : accumulator,
+        0
+      ),
+      workerNodes: this.workerNodes.length,
+      ...(this.type === PoolTypes.dynamic && {
+        dynamicWorkerNodes: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            workerNode.info.dynamic ? accumulator + 1 : accumulator,
+          0
+        ),
+      }),
+      ...(this.opts.enableTasksQueue === true && {
+        backPressure: this.backPressure,
+        backPressureWorkerNodes: this.workerNodes.reduce(
+          (accumulator, _, workerNodeKey) =>
+            this.isWorkerNodeBackPressured(workerNodeKey)
+              ? accumulator + 1
+              : accumulator,
+          0
+        ),
+        maxQueuedTasks: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            accumulator + (workerNode.usage.tasks.maxQueued ?? 0),
+          0
+        ),
+        queuedTasks: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            accumulator + workerNode.usage.tasks.queued,
+          0
+        ),
+        stealingWorkerNodes: this.workerNodes.reduce(
+          (accumulator, _, workerNodeKey) =>
+            this.isWorkerNodeStealing(workerNodeKey)
+              ? accumulator + 1
+              : accumulator,
+          0
+        ),
+        stolenTasks: this.workerNodes.reduce(
+          (accumulator, workerNode) =>
+            accumulator + workerNode.usage.tasks.stolen,
+          0
+        ),
+      }),
+      ...(this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .runTime.aggregate === true && {
+        runTime: {
+          maximum: round(
+            max(
+              ...this.workerNodes.map(
+                workerNode =>
+                  workerNode.usage.runTime.maximum ?? Number.NEGATIVE_INFINITY
+              )
+            )
           ),
-      ])
-    }
-
-  /**
-   * 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.
-   */
-  private readonly getWorkerNodeTaskFunctionPriority = (
-    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 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 readonly handleWorkerNodeBackPressureEvent = (
-    eventDetail: WorkerNodeEventDetail
-  ): void => {
-    if (
-      this.cannotStealTask() ||
-      this.backPressure ||
-      this.isStealingRatioReached()
-    ) {
-      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) {
-        break
-      }
-      if (
-        workerNode.info.id !== workerId &&
-        !workerNode.info.backPressureStealing &&
-        workerNode.usage.tasks.queued <
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.opts.tasksQueueOptions!.size! - sizeOffset
-      ) {
-        workerNode.info.backPressureStealing = true
-        this.stealTask(sourceWorkerNode, workerNodeKey)
-        workerNode.info.backPressureStealing = false
-      }
-    }
-  }
-
-  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"
-      )
-    }
-    const workerNode = this.workerNodes[workerNodeKey]
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (workerNode == null) {
-      return
-    }
-    if (
-      !workerNode.info.continuousStealing &&
-      (this.cannotStealTask() || this.isStealingRatioReached())
-    ) {
-      return
-    }
-    const workerNodeTasksUsage = workerNode.usage.tasks
-    if (
-      workerNode.info.continuousStealing &&
-      !this.isWorkerNodeIdle(workerNodeKey)
-    ) {
-      workerNode.info.continuousStealing = false
-      if (workerNodeTasksUsage.sequentiallyStolen > 0) {
-        this.resetTaskSequentiallyStolenStatisticsWorkerUsage(
-          workerNodeKey,
-          previousStolenTask?.name
-        )
-      }
-      return
+          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
+                )
+              )
+            ),
+          },
+        },
+      }),
     }
-    workerNode.info.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 isStealingRatioReached = (): boolean => {
-    return (
-      this.opts.tasksQueueOptions?.tasksStealingRatio === 0 ||
-      (this.info.stealingWorkerNodes ?? 0) >
-        Math.ceil(
-          this.workerNodes.length *
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            this.opts.tasksQueueOptions!.tasksStealingRatio!
-        )
-    )
   }
 
   /**
-   * 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.
+   * The task execution response promise map:
+   * - `key`: The message id of each submitted task.
+   * - `value`: An object that contains task's worker node key, execution response promise resolve and reject callbacks, async resource.
+   *
+   * When we receive a message from the worker, we get a map entry with the promise resolve/reject bound to the message id.
    */
-  private startingMinimumNumberOfWorkers: boolean
+  protected promiseResponseMap: Map<
+    `${string}-${string}-${string}-${string}-${string}`,
+    PromiseResponseWrapper<Response>
+  > = new Map<
+    `${string}-${string}-${string}-${string}-${string}`,
+    PromiseResponseWrapper<Response>
+  >()
 
   /**
-   * The start timestamp of the pool.
+   * Worker choice strategies context referencing worker choice algorithms implementation.
    */
-  private startTimestamp?: number
-
-  private readonly stealTask = (
-    sourceWorkerNode: IWorkerNode<Worker, Data>,
-    destinationWorkerNodeKey: number
-  ): Task<Data> | undefined => {
-    const destinationWorkerNode = this.workerNodes[destinationWorkerNodeKey]
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (destinationWorkerNode == null) {
-      return
-    }
-    // Avoid cross and cascade task stealing. Could be smarter by checking stealing/stolen worker ids pair.
-    if (
-      !sourceWorkerNode.info.ready ||
-      sourceWorkerNode.info.stolen ||
-      sourceWorkerNode.info.stealing ||
-      !destinationWorkerNode.info.ready ||
-      destinationWorkerNode.info.stolen ||
-      destinationWorkerNode.info.stealing
-    ) {
-      return
-    }
-    destinationWorkerNode.info.stealing = true
-    sourceWorkerNode.info.stolen = true
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    const stolenTask = sourceWorkerNode.dequeueLastPrioritizedTask()!
-    sourceWorkerNode.info.stolen = false
-    destinationWorkerNode.info.stealing = false
-    this.handleTask(destinationWorkerNodeKey, stolenTask)
-    this.updateTaskStolenStatisticsWorkerUsage(
-      destinationWorkerNodeKey,
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      stolenTask.name!
-    )
-    return stolenTask
-  }
+  protected workerChoiceStrategiesContext?: WorkerChoiceStrategiesContext<
+    Worker,
+    Data,
+    Response
+  >
 
   /**
-   * The task functions added at runtime map:
-   * - `key`: The task function name.
-   * - `value`: The task function object.
+   * Whether the pool is back pressured or not.
+   * @returns The pool back pressure boolean status.
    */
-  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 emitter?: EventEmitterAsyncResource
-
-  /** @inheritDoc */
-  public readonly workerNodes: IWorkerNode<Worker, Data>[] = []
+  protected abstract get backPressure (): boolean
 
   /**
-   * 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)
+   * Whether the pool is busy or not.
+   * @returns The pool busyness boolean status.
+   */
+  protected abstract get busy (): boolean
 
-    this.chooseWorkerNode = this.chooseWorkerNode.bind(this)
-    this.executeTask = this.executeTask.bind(this)
-    this.enqueueTask = this.enqueueTask.bind(this)
+  /**
+   * The pool type.
+   *
+   * If it is `'dynamic'`, it provides the `max` property.
+   */
+  protected abstract get type (): PoolType
 
-    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
-      [this.opts.workerChoiceStrategy!],
-      this.opts.workerChoiceStrategyOptions
-    )
+  /**
+   * The worker type.
+   */
+  protected abstract get worker (): WorkerType
 
-    this.setupHook()
+  /**
+   * Whether the pool back pressure event has been emitted or not.
+   */
+  private backPressureEventEmitted: boolean
 
-    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
+  /**
+   * Whether the pool busy event has been emitted or not.
+   */
+  private busyEventEmitted: boolean
 
-    this.started = false
-    this.starting = false
-    this.destroying = false
-    this.readyEventEmitted = false
-    this.busyEventEmitted = false
-    this.backPressureEventEmitted = false
-    this.startingMinimumNumberOfWorkers = false
-    if (this.opts.startWorkers === true) {
-      this.start()
-    }
-  }
+  /**
+   * Whether the pool is destroying or not.
+   */
+  private destroying: boolean
 
   /**
-   * Hook executed after the worker task execution.
-   * Can be overridden.
-   * @param workerNodeKey - The worker node key.
-   * @param message - The received message.
+   * Whether the pool ready event has been emitted or not.
    */
-  protected afterTaskExecutionHook (
-    workerNodeKey: number,
-    message: MessageValue<Response>
-  ): 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
-      )
-      needWorkerChoiceStrategiesUpdate = true
-    }
-    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 (needWorkerChoiceStrategiesUpdate) {
-      this.workerChoiceStrategiesContext?.update(workerNodeKey)
-    }
-  }
+  private readyEventEmitted: boolean
 
   /**
-   * Method hooked up after a worker node has been newly created.
-   * Can be overridden.
-   * @param workerNodeKey - The newly created worker node key.
+   * Whether the pool is started or not.
    */
-  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
-        )
-      }
-    }
-  }
+  private started: boolean
 
   /**
-   * Hook executed before the worker task execution.
-   * Can be overridden.
-   * @param workerNodeKey - The worker node key.
-   * @param task - The task to execute.
+   * Whether the pool is starting or not.
    */
-  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.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 starting: boolean
 
   /**
-   * Emits dynamic worker creation events.
+   * Whether the minimum number of workers is starting or not.
    */
-  protected abstract checkAndEmitDynamicWorkerCreationEvents (): void
+  private startingMinimumNumberOfWorkers: boolean
 
   /**
-   * Emits dynamic worker destruction events.
+   * The start timestamp of the pool.
    */
-  protected abstract checkAndEmitDynamicWorkerDestructionEvents (): void
+  private startTimestamp?: number
 
   /**
-   * Creates a new, completely set up dynamic worker node.
-   * @returns New, completely set up dynamic worker node key.
+   * The task functions added at runtime map:
+   * - `key`: The task function name.
+   * - `value`: The task function object.
    */
-  protected createAndSetupDynamicWorkerNode (): number {
-    const workerNodeKey = this.createAndSetupWorkerNode()
-    this.registerWorkerMessageListener(workerNodeKey, message => {
-      this.checkMessageWorkerId(message)
-      const localWorkerNodeKey = this.getWorkerNodeKeyByWorkerId(
-        message.workerId
-      )
-      // Kill message received from worker
-      if (
-        isKillBehavior(KillBehaviors.HARD, message.kill) ||
-        (isKillBehavior(KillBehaviors.SOFT, message.kill) &&
-          this.isWorkerNodeIdle(localWorkerNodeKey) &&
-          !this.isWorkerNodeStealing(localWorkerNodeKey))
-      ) {
-        // 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)
-        })
-      }
-    }
-    const workerNode = this.workerNodes[workerNodeKey]
-    workerNode.info.dynamic = true
-    if (
-      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerReady ===
-      true
-    ) {
-      workerNode.info.ready = true
+  private readonly taskFunctions: Map<
+    string,
+    TaskFunctionObject<Data, Response>
+  >
+
+  /**
+   * Whether the pool is ready or not.
+   * @returns The pool readiness boolean status.
+   */
+  private get ready (): boolean {
+    if (!this.started) {
+      return false
     }
-    this.initWorkerNodeUsage(workerNode)
-    this.checkAndEmitDynamicWorkerCreationEvents()
-    return workerNodeKey
+    return (
+      this.workerNodes.reduce(
+        (accumulator, workerNode) =>
+          !workerNode.info.dynamic && workerNode.info.ready
+            ? accumulator + 1
+            : accumulator,
+        0
+      ) >= this.minimumNumberOfWorkers
+    )
   }
 
   /**
-   * Creates a new, completely set up worker node.
-   * @returns New, completely set up worker node key.
+   * The approximate pool utilization.
+   * @returns The pool utilization.
    */
-  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
+  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
     )
-    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))
-      }
-      // 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
+    const totalTasksWaitTime = this.workerNodes.reduce(
+      (accumulator, workerNode) =>
+        accumulator + (workerNode.usage.waitTime.aggregate ?? 0),
+      0
     )
-    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
+    return (totalTasksRunTime + totalTasksWaitTime) / poolTimeCapacity
   }
 
   /**
-   * Deregisters a listener callback on the worker given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @param listener - The message listener callback.
+   * 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.
    */
-  protected abstract deregisterWorkerMessageListener<
-    Message extends Data | Response
-  >(
-    workerNodeKey: number,
-    listener: (message: MessageValue<Message>) => void
-  ): void
+  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)
 
-  /**
-   * Terminates the worker node given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   */
-  protected async destroyWorkerNode (workerNodeKey: number): Promise<void> {
-    this.flagWorkerNodeAsNotReady(workerNodeKey)
-    const flushedTasks = this.flushTasksQueue(workerNodeKey)
-    const workerNode = this.workerNodes[workerNodeKey]
-    await waitWorkerNodeEvents(
-      workerNode,
-      'taskFinished',
-      flushedTasks,
-      this.opts.tasksQueueOptions?.tasksFinishedTimeout ??
-        getDefaultTasksQueueOptions(
-          this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
-        ).tasksFinishedTimeout
+    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
+      [this.opts.workerChoiceStrategy!],
+      this.opts.workerChoiceStrategyOptions
     )
-    await this.sendKillMessageToWorker(workerNodeKey)
-    await workerNode.terminate()
+
+    this.setupHook()
+
+    this.taskFunctions = new Map<string, TaskFunctionObject<Data, Response>>()
+
+    this.started = false
+    this.starting = false
+    this.destroying = false
+    this.readyEventEmitted = false
+    this.busyEventEmitted = false
+    this.backPressureEventEmitted = false
+    this.startingMinimumNumberOfWorkers = false
+    if (this.opts.startWorkers === true) {
+      this.start()
+    }
   }
 
-  protected flagWorkerNodeAsNotReady (workerNodeKey: number): void {
-    const workerInfo = this.getWorkerInfo(workerNodeKey)
-    if (workerInfo != null) {
-      workerInfo.ready = false
+  /** @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({
+      taskFunction: fn.taskFunction.toString(),
+      taskFunctionOperation: 'add',
+      taskFunctionProperties: buildTaskFunctionProperties(name, fn),
+    })
+    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 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')
+    }
+    if (this.destroying) {
+      throw new Error('Cannot destroy an already destroying pool')
+    }
+    this.destroying = true
+    await Promise.all(
+      this.workerNodes.map(async (_, workerNodeKey) => {
+        await this.destroyWorkerNode(workerNodeKey)
+      })
+    )
+    if (this.emitter != null) {
+      this.emitter.emit(PoolEvents.destroy, this.info)
+      this.emitter.emitDestroy()
+      this.readyEventEmitted = false
     }
+    delete this.startTimestamp
+    this.destroying = false
+    this.started = 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
+  /** @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')
     }
-    this.workerNodes[workerNodeKey].clearTasksQueue()
-    return flushedTasks
-  }
-
-  /**
-   * Gets the worker information given its worker node key.
-   * @param workerNodeKey - The worker node key.
-   * @returns The worker information.
-   */
-  protected getWorkerInfo (workerNodeKey: number): undefined | WorkerInfo {
-    return this.workerNodes[workerNodeKey]?.info
+    if (transferList != null && !Array.isArray(transferList)) {
+      throw new TypeError('transferList argument must be an array')
+    }
+    return await this.internalExecute(data, name, transferList)
   }
 
-  /**
-   * Whether the worker nodes are back pressured or not.
-   * @returns Worker nodes back pressure boolean status.
-   */
-  protected internalBackPressure (): boolean {
-    return (
-      this.workerNodes.reduce(
-        (accumulator, _, workerNodeKey) =>
-          this.isWorkerNodeBackPressured(workerNodeKey)
-            ? accumulator + 1
-            : accumulator,
-        0
-      ) === this.workerNodes.length
+  /** @inheritDoc */
+  public hasTaskFunction (name: string): boolean {
+    return this.listTaskFunctionsProperties().some(
+      taskFunctionProperties => taskFunctionProperties.name === name
     )
   }
 
-  /**
-   * Whether worker nodes are executing concurrently their tasks quota or not.
-   * @returns Worker nodes busyness boolean status.
-   */
-  protected internalBusy (): boolean {
-    return (
-      this.workerNodes.reduce(
-        (accumulator, _, workerNodeKey) =>
-          this.isWorkerNodeBusy(workerNodeKey) ? accumulator + 1 : accumulator,
-        0
-      ) === this.workerNodes.length
-    )
+  /** @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 []
   }
 
-  /**
-   * Returns whether the worker is the main worker or not.
-   * @returns `true` if the worker is the main worker, `false` otherwise.
-   */
-  protected abstract isMain (): boolean
-
-  /**
-   * 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
-
-  /**
-   * 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>,
+  /** @inheritDoc */
+  public async mapExecute (
+    data: Iterable<Data>,
+    name?: string,
     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 */
+  ): 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)
+      )
+    )
   }
 
-  /**
-   * 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')
+  /** @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'
+      )
     }
-    return workerNodeKey
+    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()
+    )
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.sendStatisticsMessageToWorker(workerNodeKey)
+    }
+    return opResult
   }
 
-  private buildTasksQueueOptions (
-    tasksQueueOptions: TasksQueueOptions | undefined
-  ): TasksQueueOptions {
-    return {
-      ...getDefaultTasksQueueOptions(
-        this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
+  /** @inheritDoc */
+  public async setDefaultTaskFunction (name: string): Promise<boolean> {
+    return await this.sendTaskFunctionOperationToWorkers({
+      taskFunctionOperation: 'default',
+      taskFunctionProperties: buildTaskFunctionProperties(
+        name,
+        this.taskFunctions.get(name)
       ),
-      ...this.opts.tasksQueueOptions,
-      ...tasksQueueOptions,
-    }
+    })
   }
 
-  private cannotStealTask (): boolean {
-    return this.workerNodes.length <= 1 || this.info.queuedTasks === 0
+  /** @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 checkAndEmitReadyEvent (): void {
-    if (this.emitter != null && !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
+      )
+    }
+    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)
+      }
     }
   }
 
-  private checkAndEmitTaskDequeuingEvents (): void {
-    if (
-      this.emitter != null &&
-      this.backPressureEventEmitted &&
-      !this.backPressure
-    ) {
-      this.emitter.emit(PoolEvents.backPressureEnd, this.info)
-      this.backPressureEventEmitted = false
+  /** @inheritDoc */
+  public setWorkerChoiceStrategyOptions (
+    workerChoiceStrategyOptions: undefined | WorkerChoiceStrategyOptions
+  ): 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
   }
 
-  private checkAndEmitTaskExecutionEvents (): void {
-    if (this.emitter != null && !this.busyEventEmitted && this.busy) {
-      this.emitter.emit(PoolEvents.busy, this.info)
-      this.busyEventEmitted = true
+  /** @inheritdoc */
+  public start (): void {
+    if (this.started) {
+      throw new Error('Cannot start an already started pool')
     }
-  }
-
-  private checkAndEmitTaskExecutionFinishedEvents (): void {
-    if (this.emitter != null && this.busyEventEmitted && !this.busy) {
-      this.emitter.emit(PoolEvents.busyEnd, this.info)
-      this.busyEventEmitted = false
+    if (this.starting) {
+      throw new Error('Cannot start an already starting pool')
     }
-  }
-
-  private checkAndEmitTaskQueuingEvents (): void {
-    if (
-      this.emitter != null &&
-      !this.backPressureEventEmitted &&
-      this.backPressure
-    ) {
-      this.emitter.emit(PoolEvents.backPressure, this.info)
-      this.backPressureEventEmitted = true
+    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
   }
 
   /**
-   * Checks if the worker id sent in the received message from a worker is valid.
+   * Hook executed after the worker task execution.
+   * Can be overridden.
+   * @param workerNodeKey - The worker node key.
    * @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
+  protected afterTaskExecutionHook (
+    workerNodeKey: number,
+    message: MessageValue<Response>
   ): void {
-    if (minimumNumberOfWorkers == null) {
-      throw new Error(
-        'Cannot instantiate a pool without specifying the number of workers'
+    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
       )
-    } else if (!Number.isSafeInteger(minimumNumberOfWorkers)) {
-      throw new TypeError(
-        'Cannot instantiate a pool with a non safe integer number of workers'
+      updateEluWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        workerUsage,
+        message
       )
-    } else if (minimumNumberOfWorkers < 0) {
-      throw new RangeError(
-        'Cannot instantiate a pool with a negative number of workers'
+      needWorkerChoiceStrategiesUpdate = true
+    }
+    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
       )
-    } else if (this.type === PoolTypes.fixed && minimumNumberOfWorkers === 0) {
-      throw new RangeError('Cannot instantiate a fixed pool with zero worker')
+      updateEluWorkerUsage(
+        this.workerChoiceStrategiesContext,
+        taskFunctionWorkerUsage,
+        message
+      )
+      needWorkerChoiceStrategiesUpdate = true
+    }
+    if (needWorkerChoiceStrategiesUpdate) {
+      this.workerChoiceStrategiesContext?.update(workerNodeKey)
     }
   }
 
-  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
+  /**
+   * 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)
+    if (this.opts.enableTasksQueue === true) {
+      if (this.opts.tasksQueueOptions?.taskStealing === true) {
+        this.workerNodes[workerNodeKey].on(
+          'idle',
+          this.handleWorkerNodeIdleEvent
+        )
       }
-      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
+      if (this.opts.tasksQueueOptions?.tasksStealingOnBackPressure === true) {
+        this.workerNodes[workerNodeKey].on(
+          'backPressure',
+          this.handleWorkerNodeBackPressureEvent
         )
       }
-    } else {
-      throw new TypeError('Invalid pool options: must be a plain object')
     }
   }
 
-  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 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
       )
     }
-  }
-
-  private checkValidWorkerChoiceStrategyOptions (
-    workerChoiceStrategyOptions: undefined | WorkerChoiceStrategyOptions
-  ): void {
     if (
-      workerChoiceStrategyOptions != null &&
-      !isPlainObject(workerChoiceStrategyOptions)
+      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      this.workerNodes[workerNodeKey].getTaskFunctionWorkerUsage(task.name!) !=
+        null
     ) {
-      throw new TypeError(
-        'Invalid worker choice strategy options: must be a plain object'
+      // 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
       )
     }
-    if (
-      workerChoiceStrategyOptions?.weights != null &&
-      Object.keys(workerChoiceStrategyOptions.weights).length !==
-        (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
-    ) {
-      throw new Error(
-        'Invalid worker choice strategy options: must have a weight for each worker node'
+  }
+
+  /**
+   * Emits dynamic worker creation events.
+   */
+  protected abstract checkAndEmitDynamicWorkerCreationEvents (): void
+
+  /**
+   * Emits dynamic worker destruction events.
+   */
+  protected abstract checkAndEmitDynamicWorkerDestructionEvents (): 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
       )
+      // Kill message received from worker
+      if (
+        isKillBehavior(KillBehaviors.HARD, message.kill) ||
+        (isKillBehavior(KillBehaviors.SOFT, message.kill) &&
+          this.isWorkerNodeIdle(localWorkerNodeKey) &&
+          !this.isWorkerNodeStealing(localWorkerNodeKey))
+      ) {
+        // 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)
+        })
+      }
     }
+    const workerNode = this.workerNodes[workerNodeKey]
+    workerNode.info.dynamic = true
     if (
-      workerChoiceStrategyOptions?.measurement != null &&
-      !Object.values(Measurements).includes(
-        workerChoiceStrategyOptions.measurement
-      )
+      this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerReady ===
+      true
     ) {
-      throw new Error(
-        `Invalid worker choice strategy options: invalid measurement '${workerChoiceStrategyOptions.measurement}'`
-      )
+      workerNode.info.ready = true
     }
+    this.initWorkerNodeUsage(workerNode)
+    this.checkAndEmitDynamicWorkerCreationEvents()
+    return workerNodeKey
   }
 
   /**
-   * Chooses a worker node for the next task.
-   * @param name - The task function name.
-   * @returns The chosen worker node key.
+   * Creates a new, completely set up worker node.
+   * @returns New, completely set up worker node key.
    */
-  private chooseWorkerNode (name?: string): number {
-    if (this.shallCreateDynamicWorker()) {
-      const workerNodeKey = this.createAndSetupDynamicWorkerNode()
+  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.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerUsage ===
-        true
+        this.started &&
+        !this.destroying &&
+        this.opts.restartWorkerOnError === true
       ) {
-        return workerNodeKey
+        if (workerNode.info.dynamic) {
+          this.createAndSetupDynamicWorkerNode()
+        } else if (!this.startingMinimumNumberOfWorkers) {
+          this.startMinimumNumberOfWorkers(true)
+        }
       }
-    }
-    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-    return this.workerChoiceStrategiesContext!.execute(
-      this.getTaskFunctionWorkerChoiceStrategy(name)
-    )
-  }
-
-  /**
-   * 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,
+      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
     )
-    // 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 {
-    const task = this.workerNodes[workerNodeKey].dequeueTask()
-    this.checkAndEmitTaskDequeuingEvents()
-    return task
-  }
-
-  private enqueueTask (workerNodeKey: number, task: Task<Data>): number {
-    const tasksQueueSize = this.workerNodes[workerNodeKey].enqueueTask(task)
-    this.checkAndEmitTaskQueuingEvents()
-    return tasksQueueSize
+    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
   }
 
   /**
-   * Executes the given task on the worker given its worker node key.
+   * Deregisters 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.
    */
-  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
-    )
-  }
+  protected abstract deregisterWorkerMessageListener<
+    Message extends Data | Response
+  >(
+    workerNodeKey: number,
+    listener: (message: MessageValue<Message>) => void
+  ): void
 
   /**
-   * 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.
+   * Terminates the worker node given its worker node key.
+   * @param workerNodeKey - The worker node key.
    */
-  private getWorkerNodeKeyByWorkerId (workerId: number | undefined): number {
-    return this.workerNodes.findIndex(
-      workerNode => workerNode.info.id === workerId
+  protected async destroyWorkerNode (workerNodeKey: number): Promise<void> {
+    this.flagWorkerNodeAsNotReady(workerNodeKey)
+    const flushedTasks = this.flushTasksQueue(workerNodeKey)
+    const workerNode = this.workerNodes[workerNodeKey]
+    await waitWorkerNodeEvents(
+      workerNode,
+      'taskFinished',
+      flushedTasks,
+      this.opts.tasksQueueOptions?.tasksFinishedTimeout ??
+        getDefaultTasksQueueOptions(
+          this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
+        ).tasksFinishedTimeout
     )
+    await this.sendKillMessageToWorker(workerNodeKey)
+    await workerNode.terminate()
   }
 
-  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)
-        const error = this.handleWorkerError(workerError)
-        asyncResource != null
-          ? asyncResource.runInAsyncScope(reject, this.emitter, error)
-          : reject(error)
-      } else {
-        asyncResource != null
-          ? asyncResource.runInAsyncScope(resolve, this.emitter, data)
-          : resolve(data as Response)
-      }
-      asyncResource?.emitDestroy()
-      this.afterTaskExecutionHook(workerNodeKey, message)
-      queueMicrotask(() => {
-        this.checkAndEmitTaskExecutionFinishedEvents()
-        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-        workerNode?.emit('taskFinished', taskId)
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        this.promiseResponseMap.delete(taskId!)
-        if (this.opts.enableTasksQueue === true && !this.destroying) {
-          if (
-            !this.isWorkerNodeBusy(workerNodeKey) &&
-            this.tasksQueueSize(workerNodeKey) > 0
-          ) {
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            this.executeTask(workerNodeKey, this.dequeueTask(workerNodeKey)!)
-          }
-          if (this.isWorkerNodeIdle(workerNodeKey)) {
-            workerNode.emit('idle', {
-              workerNodeKey,
-            })
-          }
-        }
-        if (this.shallCreateDynamicWorker()) {
-          this.createAndSetupDynamicWorkerNode()
-        }
-      })
+  protected flagWorkerNodeAsNotReady (workerNodeKey: number): void {
+    const workerInfo = this.getWorkerInfo(workerNodeKey)
+    if (workerInfo != null) {
+      workerInfo.ready = false
     }
   }
 
-  private handleWorkerError (workerError: WorkerError): Error {
-    if (workerError.error != null) {
-      return workerError.error
+  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
     }
-    const error = new Error(workerError.message)
-    error.stack = workerError.stack
-    return error
+    this.workerNodes[workerNodeKey].clearTasksQueue()
+    return flushedTasks
   }
 
-  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()
+  /**
+   * Gets the worker information given its worker node key.
+   * @param workerNodeKey - The worker node key.
+   * @returns The worker information.
+   */
+  protected getWorkerInfo (workerNodeKey: number): undefined | WorkerInfo {
+    return this.workerNodes[workerNodeKey]?.info
   }
 
-  private initEventEmitter (): void {
-    this.emitter = new EventEmitterAsyncResource({
-      name: `poolifier:${this.type}-${this.worker}-pool`,
-    })
+  /**
+   * Whether the worker nodes are back pressured or not.
+   * @returns Worker nodes back pressure boolean status.
+   */
+  protected internalBackPressure (): boolean {
+    return (
+      this.workerNodes.reduce(
+        (accumulator, _, workerNodeKey) =>
+          this.isWorkerNodeBackPressured(workerNodeKey)
+            ? accumulator + 1
+            : accumulator,
+        0
+      ) === this.workerNodes.length
+    )
   }
 
   /**
-   * Initializes the worker node usage with sensible default values gathered during runtime.
-   * @param workerNode - The worker node.
+   * Whether worker nodes are executing concurrently their tasks quota or not.
+   * @returns Worker nodes busyness boolean status.
    */
-  private initWorkerNodeUsage (workerNode: IWorkerNode<Worker, Data>): void {
-    if (
-      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-        .runTime.aggregate === true
-    ) {
-      workerNode.usage.runTime.aggregate = min(
-        ...this.workerNodes.map(
-          workerNode =>
-            workerNode.usage.runTime.aggregate ?? Number.POSITIVE_INFINITY
-        )
-      )
-    }
-    if (
-      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-        .waitTime.aggregate === true
-    ) {
-      workerNode.usage.waitTime.aggregate = min(
-        ...this.workerNodes.map(
-          workerNode =>
-            workerNode.usage.waitTime.aggregate ?? Number.POSITIVE_INFINITY
-        )
-      )
-    }
-    if (
-      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements().elu
-        .aggregate === true
-    ) {
-      workerNode.usage.elu.active.aggregate = min(
-        ...this.workerNodes.map(
-          workerNode =>
-            workerNode.usage.elu.active.aggregate ?? Number.POSITIVE_INFINITY
-        )
-      )
-    }
+  protected internalBusy (): boolean {
+    return (
+      this.workerNodes.reduce(
+        (accumulator, _, workerNodeKey) =>
+          this.isWorkerNodeBusy(workerNodeKey) ? accumulator + 1 : accumulator,
+        0
+      ) === this.workerNodes.length
+    )
   }
 
-  private async internalExecute (
-    data?: Data,
-    name?: string,
+  /**
+   * Returns whether the worker is the main worker or not.
+   * @returns `true` if the worker is the main worker, `false` otherwise.
+   */
+  protected abstract isMain (): boolean
+
+  /**
+   * 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
+
+  /**
+   * 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[]
-  ): 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,
-      }
-      if (
-        this.opts.enableTasksQueue === false ||
-        (this.opts.enableTasksQueue === true &&
-          this.shallExecuteTask(workerNodeKey))
-      ) {
-        this.executeTask(workerNodeKey, task)
-      } else {
-        this.enqueueTask(workerNodeKey, task)
+  ): 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
+
+  /**
+   * 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 { 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)
       }
-      queueMicrotask(() => {
-        // 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,
-            }),
-          }),
-        })
-      })
-    })
+    } else if (taskId != null) {
+      // Task execution response received from worker
+      this.handleTaskExecutionResponse(message)
+    }
   }
 
-  private isWorkerNodeBackPressured (workerNodeKey: number): boolean {
-    const workerNode = this.workerNodes[workerNodeKey]
-    return workerNode.info.ready && workerNode.info.backPressure
+  /**
+   * 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 isWorkerNodeBusy (workerNodeKey: number): boolean {
-    const workerNode = this.workerNodes[workerNodeKey]
-    if (this.opts.enableTasksQueue === true) {
-      return (
-        workerNode.info.ready &&
-        workerNode.usage.tasks.executing >=
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          this.opts.tasksQueueOptions!.concurrency!
-      )
+  private buildTasksQueueOptions (
+    tasksQueueOptions: TasksQueueOptions | undefined
+  ): TasksQueueOptions {
+    return {
+      ...getDefaultTasksQueueOptions(
+        this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers
+      ),
+      ...this.opts.tasksQueueOptions,
+      ...tasksQueueOptions,
     }
-    return workerNode.info.ready && workerNode.usage.tasks.executing > 0
   }
 
-  private isWorkerNodeIdle (workerNodeKey: number): boolean {
-    const workerNode = this.workerNodes[workerNodeKey]
-    if (this.opts.enableTasksQueue === true) {
-      return (
-        workerNode.info.ready &&
-        workerNode.usage.tasks.executing === 0 &&
-        this.tasksQueueSize(workerNodeKey) === 0
-      )
-    }
-    return workerNode.info.ready && workerNode.usage.tasks.executing === 0
+  private cannotStealTask (): boolean {
+    return this.workerNodes.length <= 1 || this.info.queuedTasks === 0
   }
 
-  private isWorkerNodeStealing (workerNodeKey: number): boolean {
-    const workerNode = this.workerNodes[workerNodeKey]
-    return (
-      workerNode.info.ready &&
-      (workerNode.info.continuousStealing ||
-        workerNode.info.backPressureStealing)
-    )
+  private checkAndEmitReadyEvent (): void {
+    if (this.emitter != null && !this.readyEventEmitted && this.ready) {
+      this.emitter.emit(PoolEvents.ready, this.info)
+      this.readyEventEmitted = true
+    }
   }
 
-  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)!
-      )
+  private checkAndEmitTaskDequeuingEvents (): void {
+    if (
+      this.emitter != null &&
+      this.backPressureEventEmitted &&
+      !this.backPressure
+    ) {
+      this.emitter.emit(PoolEvents.backPressureEnd, this.info)
+      this.backPressureEventEmitted = false
     }
   }
 
-  /**
-   * Removes the worker node from the pool worker nodes.
-   * @param workerNode - The worker node.
-   */
-  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)
-      workerNode.info.dynamic &&
-        this.checkAndEmitDynamicWorkerDestructionEvents()
+  private checkAndEmitTaskExecutionEvents (): void {
+    if (this.emitter != null && !this.busyEventEmitted && this.busy) {
+      this.emitter.emit(PoolEvents.busy, this.info)
+      this.busyEventEmitted = true
     }
   }
 
-  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
+  private checkAndEmitTaskExecutionFinishedEvents (): void {
+    if (this.emitter != null && this.busyEventEmitted && !this.busy) {
+      this.emitter.emit(PoolEvents.busyEnd, this.info)
+      this.busyEventEmitted = false
     }
+  }
+
+  private checkAndEmitTaskQueuingEvents (): void {
     if (
-      taskName != null &&
-      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
-      workerNode.getTaskFunctionWorkerUsage(taskName) != null
+      this.emitter != null &&
+      !this.backPressureEventEmitted &&
+      this.backPressure
     ) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      workerNode.getTaskFunctionWorkerUsage(
-        taskName
-      )!.tasks.sequentiallyStolen = 0
+      this.emitter.emit(PoolEvents.backPressure, this.info)
+      this.backPressureEventEmitted = true
     }
   }
 
-  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.
-   * @param workerNodeKey - The worker node key.
+   * 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 sendStatisticsMessageToWorker (workerNodeKey: number): void {
-    this.sendToWorker(workerNodeKey, {
-      statistics: {
-        elu:
-          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-            .elu.aggregate ?? false,
-        runTime:
-          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
-            .runTime.aggregate ?? false,
-      },
-    })
+  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 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
+  private checkMinimumNumberOfWorkers (
+    minimumNumberOfWorkers: number | undefined
+  ): void {
+    if (minimumNumberOfWorkers == null) {
+      throw new Error(
+        'Cannot instantiate a pool without specifying the number of workers'
       )
-      this.sendToWorker(workerNodeKey, message)
-    })
+    } 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 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
-            )
-          }
-        }
+  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
       }
-      for (const workerNodeKey of this.workerNodes.keys()) {
-        this.registerWorkerMessageListener(
-          workerNodeKey,
-          taskFunctionOperationsListener
+      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
         )
-        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
+    } else {
+      throw new TypeError('Invalid pool options: must be a plain object')
     }
   }
 
-  private setTasksStealingOnBackPressure (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.workerNodes[workerNodeKey].on(
-        'backPressure',
-        this.handleWorkerNodeBackPressureEvent
+  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 setTaskStealing (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.workerNodes[workerNodeKey].on('idle', this.handleWorkerNodeIdleEvent)
+  private checkValidWorkerChoiceStrategyOptions (
+    workerChoiceStrategyOptions: undefined | WorkerChoiceStrategyOptions
+  ): void {
+    if (
+      workerChoiceStrategyOptions != null &&
+      !isPlainObject(workerChoiceStrategyOptions)
+    ) {
+      throw new TypeError(
+        'Invalid worker choice strategy options: must be a plain object'
+      )
+    }
+    if (
+      workerChoiceStrategyOptions?.weights != null &&
+      Object.keys(workerChoiceStrategyOptions.weights).length !==
+        (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
+    ) {
+      throw new Error(
+        'Invalid worker choice strategy options: must have a weight for each worker node'
+      )
+    }
+    if (
+      workerChoiceStrategyOptions?.measurement != null &&
+      !Object.values(Measurements).includes(
+        workerChoiceStrategyOptions.measurement
+      )
+    ) {
+      throw new Error(
+        `Invalid worker choice strategy options: invalid measurement '${workerChoiceStrategyOptions.measurement}'`
+      )
     }
-  }
-
-  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.
+   * Chooses a worker node for the next task.
+   * @param name - The task function name.
+   * @returns The chosen worker node key.
    */
-  private shallUpdateTaskFunctionWorkerUsage (workerNodeKey: number): boolean {
-    const workerInfo = this.getWorkerInfo(workerNodeKey)
-    return (
-      workerInfo != null &&
-      Array.isArray(workerInfo.taskFunctionsProperties) &&
-      workerInfo.taskFunctionsProperties.length > 2
+  private chooseWorkerNode (name?: string): number {
+    if (this.shallCreateDynamicWorker()) {
+      const workerNodeKey = this.createAndSetupDynamicWorkerNode()
+      if (
+        this.workerChoiceStrategiesContext?.getPolicy().dynamicWorkerUsage ===
+        true
+      ) {
+        return workerNodeKey
+      }
+    }
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    return this.workerChoiceStrategiesContext!.execute(
+      this.getTaskFunctionWorkerChoiceStrategy(name)
     )
   }
 
   /**
-   * Starts the minimum number of workers.
-   * @param initWorkerNodeUsage - Whether to initialize the worker node usage or not. @defaultValue false
+   * Creates a worker node.
+   * @returns The created worker node.
    */
-  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])
+  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
     }
-    this.startingMinimumNumberOfWorkers = false
+    return workerNode
   }
 
-  private tasksQueueSize (workerNodeKey: number): number {
-    return this.workerNodes[workerNodeKey].tasksQueueSize()
+  private dequeueTask (workerNodeKey: number): Task<Data> | undefined {
+    const task = this.workerNodes[workerNodeKey].dequeueTask()
+    this.checkAndEmitTaskDequeuingEvents()
+    return task
   }
 
-  private unsetTasksStealingOnBackPressure (): void {
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.workerNodes[workerNodeKey].off(
-        'backPressure',
-        this.handleWorkerNodeBackPressureEvent
-      )
-    }
+  private enqueueTask (workerNodeKey: number, task: Task<Data>): number {
+    const tasksQueueSize = this.workerNodes[workerNodeKey].enqueueTask(task)
+    this.checkAndEmitTaskQueuingEvents()
+    return tasksQueueSize
   }
 
-  private unsetTaskStealing (): void {
+  /**
+   * 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.workerNodes[workerNodeKey].off(
-        'idle',
-        this.handleWorkerNodeIdleEvent
-      )
+      this.flushTasksQueue(workerNodeKey)
     }
   }
 
-  private updateTaskSequentiallyStolenStatisticsWorkerUsage (
-    workerNodeKey: number,
-    taskName?: string,
-    previousTaskName?: string
-  ): void {
-    const workerNode = this.workerNodes[workerNodeKey]
-    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    if (workerNode?.usage != null && taskName != null) {
-      ++workerNode.usage.tasks.sequentiallyStolen
+  /**
+   * 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
+  ): undefined | WorkerChoiceStrategy => {
+    name = name ?? DEFAULT_TASK_NAME
+    const taskFunctionsProperties = this.listTaskFunctionsProperties()
+    if (name === DEFAULT_TASK_NAME) {
+      name = taskFunctionsProperties[1]?.name
     }
-    if (
-      taskName != null &&
-      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
-      workerNode.getTaskFunctionWorkerUsage(taskName) != null
-    ) {
-      const taskFunctionWorkerUsage =
+    return taskFunctionsProperties.find(
+      (taskFunctionProperties: TaskFunctionProperties) =>
+        taskFunctionProperties.name === name
+    )?.strategy
+  }
+
+  private getTasksQueuePriority (): boolean {
+    return this.listTaskFunctionsProperties().some(
+      taskFunctionProperties => taskFunctionProperties.priority != null
+    )
+  }
+
+  /**
+   * 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
-        workerNode.getTaskFunctionWorkerUsage(taskName)!
-      if (
-        taskFunctionWorkerUsage.tasks.sequentiallyStolen === 0 ||
-        (previousTaskName != null &&
-          previousTaskName === taskName &&
-          taskFunctionWorkerUsage.tasks.sequentiallyStolen > 0)
-      ) {
-        ++taskFunctionWorkerUsage.tasks.sequentiallyStolen
-      } else if (taskFunctionWorkerUsage.tasks.sequentiallyStolen > 0) {
-        taskFunctionWorkerUsage.tasks.sequentiallyStolen = 0
-      }
+        this.opts.workerChoiceStrategy!,
+        ...this.listTaskFunctionsProperties()
+          .map(
+            (taskFunctionProperties: TaskFunctionProperties) =>
+              taskFunctionProperties.strategy
+          )
+          .filter(
+            (strategy: undefined | WorkerChoiceStrategy) => strategy != 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 updateTaskStolenStatisticsWorkerUsage (
+  /**
+   * 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.
+   */
+  private readonly getWorkerNodeTaskFunctionPriority = (
     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
+    name?: string
+  ): number | undefined => {
+    const workerInfo = this.getWorkerInfo(workerNodeKey)
+    if (workerInfo == null) {
+      return
     }
-    if (
-      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
-      workerNode.getTaskFunctionWorkerUsage(taskName) != null
-    ) {
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      ++workerNode.getTaskFunctionWorkerUsage(taskName)!.tasks.stolen
+    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
   }
 
-  /** @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')
+  /**
+   * 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
     }
-    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()
-    )
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.sendStatisticsMessageToWorker(workerNodeKey)
+    name = name ?? DEFAULT_TASK_NAME
+    if (name === DEFAULT_TASK_NAME) {
+      name = workerInfo.taskFunctionsProperties?.[1]?.name
     }
-    return opResult
+    return workerInfo.taskFunctionsProperties?.find(
+      (taskFunctionProperties: TaskFunctionProperties) =>
+        taskFunctionProperties.name === name
+    )?.strategy
   }
 
-  /** @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')
-    }
-    if (this.destroying) {
-      throw new Error('Cannot destroy an already destroying pool')
+  private handleTask (workerNodeKey: number, task: Task<Data>): void {
+    if (this.shallExecuteTask(workerNodeKey)) {
+      this.executeTask(workerNodeKey, task)
+    } else {
+      this.enqueueTask(workerNodeKey, task)
     }
-    this.destroying = true
-    await Promise.all(
-      this.workerNodes.map(async (_, workerNodeKey) => {
-        await this.destroyWorkerNode(workerNodeKey)
+  }
+
+  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)
+        const error = this.handleWorkerError(workerError)
+        asyncResource != null
+          ? asyncResource.runInAsyncScope(reject, this.emitter, error)
+          : reject(error)
+      } else {
+        asyncResource != null
+          ? asyncResource.runInAsyncScope(resolve, this.emitter, data)
+          : resolve(data as Response)
+      }
+      asyncResource?.emitDestroy()
+      this.afterTaskExecutionHook(workerNodeKey, message)
+      queueMicrotask(() => {
+        this.checkAndEmitTaskExecutionFinishedEvents()
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+        workerNode?.emit('taskFinished', taskId)
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.promiseResponseMap.delete(taskId!)
+        if (this.opts.enableTasksQueue === true && !this.destroying) {
+          if (
+            !this.isWorkerNodeBusy(workerNodeKey) &&
+            this.tasksQueueSize(workerNodeKey) > 0
+          ) {
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            this.executeTask(workerNodeKey, this.dequeueTask(workerNodeKey)!)
+          }
+          if (this.isWorkerNodeIdle(workerNodeKey)) {
+            workerNode.emit('idle', {
+              workerNodeKey,
+            })
+          }
+        }
+        if (this.shallCreateDynamicWorker()) {
+          this.createAndSetupDynamicWorkerNode()
+        }
       })
-    )
-    if (this.emitter != null) {
-      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()
+  private handleWorkerError (workerError: WorkerError): Error {
+    if (workerError.error != null) {
+      return workerError.error
     }
-    this.opts.enableTasksQueue = enable
-    this.setTasksQueueOptions(tasksQueueOptions)
+    const error = new Error(workerError.message)
+    error.stack = workerError.stack
+    return error
   }
 
-  /** @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')
+  private readonly handleWorkerNodeBackPressureEvent = (
+    eventDetail: WorkerNodeEventDetail
+  ): void => {
+    if (
+      this.cannotStealTask() ||
+      this.backPressure ||
+      this.isStealingRatioReached()
+    ) {
+      return
     }
-    if (this.destroying) {
-      throw new Error('Cannot execute a task on destroying pool')
+    const sizeOffset = 1
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    if (this.opts.tasksQueueOptions!.size! <= sizeOffset) {
+      return
     }
-    if (name != null && typeof name !== 'string') {
-      throw new TypeError('name argument must be a string')
+    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) {
+        break
+      }
+      if (
+        workerNode.info.id !== workerId &&
+        !workerNode.info.backPressureStealing &&
+        workerNode.usage.tasks.queued <
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.opts.tasksQueueOptions!.size! - sizeOffset
+      ) {
+        workerNode.info.backPressureStealing = true
+        this.stealTask(sourceWorkerNode, workerNodeKey)
+        workerNode.info.backPressureStealing = false
+      }
     }
-    if (name != null && typeof name === 'string' && name.trim().length === 0) {
-      throw new TypeError('name argument must not be an empty string')
+  }
+
+  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"
+      )
     }
-    if (transferList != null && !Array.isArray(transferList)) {
-      throw new TypeError('transferList argument must be an array')
+    const workerNode = this.workerNodes[workerNodeKey]
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (workerNode == null) {
+      return
+    }
+    if (
+      !workerNode.info.continuousStealing &&
+      (this.cannotStealTask() || this.isStealingRatioReached())
+    ) {
+      return
+    }
+    const workerNodeTasksUsage = workerNode.usage.tasks
+    if (
+      workerNode.info.continuousStealing &&
+      !this.isWorkerNodeIdle(workerNodeKey)
+    ) {
+      workerNode.info.continuousStealing = false
+      if (workerNodeTasksUsage.sequentiallyStolen > 0) {
+        this.resetTaskSequentiallyStolenStatisticsWorkerUsage(
+          workerNodeKey,
+          previousStolenTask?.name
+        )
+      }
+      return
     }
-    return await this.internalExecute(data, name, transferList)
-  }
-
-  /** @inheritDoc */
-  public hasTaskFunction (name: string): boolean {
-    return this.listTaskFunctionsProperties().some(
-      taskFunctionProperties => taskFunctionProperties.name === name
+    workerNode.info.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)
+      })
   }
 
-  /** @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
-      }
+  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`)
     }
-    return []
+    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()
   }
 
-  /** @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)
-      )
-    )
+  private initEventEmitter (): void {
+    this.emitter = new EventEmitterAsyncResource({
+      name: `poolifier:${this.type}-${this.worker}-pool`,
+    })
   }
 
-  /** @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'
+  /**
+   * Initializes the worker node usage with sensible default values gathered during runtime.
+   * @param workerNode - The worker node.
+   */
+  private initWorkerNodeUsage (workerNode: IWorkerNode<Worker, Data>): void {
+    if (
+      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .runTime.aggregate === true
+    ) {
+      workerNode.usage.runTime.aggregate = min(
+        ...this.workerNodes.map(
+          workerNode =>
+            workerNode.usage.runTime.aggregate ?? Number.POSITIVE_INFINITY
+        )
       )
     }
-    const opResult = await this.sendTaskFunctionOperationToWorkers({
-      taskFunctionOperation: 'remove',
-      taskFunctionProperties: buildTaskFunctionProperties(
-        name,
-        this.taskFunctions.get(name)
-      ),
-    })
-    for (const workerNode of this.workerNodes) {
-      workerNode.deleteTaskFunctionWorkerUsage(name)
+    if (
+      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+        .waitTime.aggregate === true
+    ) {
+      workerNode.usage.waitTime.aggregate = min(
+        ...this.workerNodes.map(
+          workerNode =>
+            workerNode.usage.waitTime.aggregate ?? Number.POSITIVE_INFINITY
+        )
+      )
     }
-    this.taskFunctions.delete(name)
-    this.workerChoiceStrategiesContext?.syncWorkerChoiceStrategies(
-      this.getWorkerChoiceStrategies()
-    )
-    for (const workerNodeKey of this.workerNodes.keys()) {
-      this.sendStatisticsMessageToWorker(workerNodeKey)
+    if (
+      this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements().elu
+        .aggregate === true
+    ) {
+      workerNode.usage.elu.active.aggregate = min(
+        ...this.workerNodes.map(
+          workerNode =>
+            workerNode.usage.elu.active.aggregate ?? Number.POSITIVE_INFINITY
+        )
+      )
     }
-    return opResult
-  }
-
-  /** @inheritDoc */
-  public async setDefaultTaskFunction (name: string): Promise<boolean> {
-    return await this.sendTaskFunctionOperationToWorkers({
-      taskFunctionOperation: 'default',
-      taskFunctionProperties: buildTaskFunctionProperties(
-        name,
-        this.taskFunctions.get(name)
-      ),
-    })
   }
 
-  /** @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()
+  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,
       }
-      if (this.opts.tasksQueueOptions.tasksStealingOnBackPressure === true) {
-        this.unsetTasksStealingOnBackPressure()
-        this.setTasksStealingOnBackPressure()
+      if (
+        this.opts.enableTasksQueue === false ||
+        (this.opts.enableTasksQueue === true &&
+          this.shallExecuteTask(workerNodeKey))
+      ) {
+        this.executeTask(workerNodeKey, task)
       } else {
-        this.unsetTasksStealingOnBackPressure()
+        this.enqueueTask(workerNodeKey, task)
       }
-    } else if (this.opts.tasksQueueOptions != null) {
-      delete this.opts.tasksQueueOptions
-    }
+      queueMicrotask(() => {
+        // 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,
+            }),
+          }),
+        })
+      })
+    })
   }
 
-  /** @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
+  private readonly isStealingRatioReached = (): boolean => {
+    return (
+      this.opts.tasksQueueOptions?.tasksStealingRatio === 0 ||
+      (this.info.stealingWorkerNodes ?? 0) >
+        Math.ceil(
+          this.workerNodes.length *
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            this.opts.tasksQueueOptions!.tasksStealingRatio!
+        )
+    )
+  }
+
+  private isWorkerNodeBackPressured (workerNodeKey: number): boolean {
+    const workerNode = this.workerNodes[workerNodeKey]
+    return workerNode.info.ready && workerNode.info.backPressure
+  }
+
+  private isWorkerNodeBusy (workerNodeKey: number): boolean {
+    const workerNode = this.workerNodes[workerNodeKey]
+    if (this.opts.enableTasksQueue === true) {
+      return (
+        workerNode.info.ready &&
+        workerNode.usage.tasks.executing >=
+          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+          this.opts.tasksQueueOptions!.concurrency!
       )
-      for (const workerNodeKey of this.workerNodes.keys()) {
-        this.sendStatisticsMessageToWorker(workerNodeKey)
-      }
     }
+    return workerNode.info.ready && workerNode.usage.tasks.executing > 0
   }
 
-  /** @inheritDoc */
-  public setWorkerChoiceStrategyOptions (
-    workerChoiceStrategyOptions: undefined | WorkerChoiceStrategyOptions
-  ): 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
+  private isWorkerNodeIdle (workerNodeKey: number): boolean {
+    const workerNode = this.workerNodes[workerNodeKey]
+    if (this.opts.enableTasksQueue === true) {
+      return (
+        workerNode.info.ready &&
+        workerNode.usage.tasks.executing === 0 &&
+        this.tasksQueueSize(workerNodeKey) === 0
       )
-      for (const workerNodeKey of this.workerNodes.keys()) {
-        this.sendStatisticsMessageToWorker(workerNodeKey)
-      }
-      return true
     }
-    return false
+    return workerNode.info.ready && workerNode.usage.tasks.executing === 0
   }
 
-  /** @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')
+  private isWorkerNodeStealing (workerNodeKey: number): boolean {
+    const workerNode = this.workerNodes[workerNodeKey]
+    return (
+      workerNode.info.ready &&
+      (workerNode.info.continuousStealing ||
+        workerNode.info.backPressureStealing)
+    )
+  }
+
+  private redistributeQueuedTasks (sourceWorkerNodeKey: number): void {
+    if (sourceWorkerNodeKey === -1 || this.cannotStealTask()) {
+      return
     }
-    if (this.destroying) {
-      throw new Error('Cannot start a destroying pool')
+    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)!
+      )
     }
-    this.starting = true
-    this.startMinimumNumberOfWorkers()
-    this.startTimestamp = performance.now()
-    this.starting = false
-    this.started = true
   }
 
   /**
-   * Whether the pool is back pressured or not.
-   * @returns The pool back pressure boolean status.
-   */
-  protected abstract get backPressure (): boolean
-
-  /**
-   * Whether the pool is busy or not.
-   * @returns The pool busyness boolean status.
+   * Removes the worker node from the pool worker nodes.
+   * @param workerNode - The worker node.
    */
-  protected abstract get busy (): boolean
+  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)
+      workerNode.info.dynamic &&
+        this.checkAndEmitDynamicWorkerDestructionEvents()
+    }
+  }
 
-  /** @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
-      ),
-      executedTasks: this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          accumulator + workerNode.usage.tasks.executed,
-        0
-      ),
-      executingTasks: this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          accumulator + workerNode.usage.tasks.executing,
-        0
-      ),
-      failedTasks: this.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          accumulator + workerNode.usage.tasks.failed,
-        0
-      ),
-      idleWorkerNodes: this.workerNodes.reduce(
-        (accumulator, _, workerNodeKey) =>
-          this.isWorkerNodeIdle(workerNodeKey) ? accumulator + 1 : accumulator,
-        0
-      ),
-      workerNodes: this.workerNodes.length,
-      ...(this.type === PoolTypes.dynamic && {
-        dynamicWorkerNodes: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            workerNode.info.dynamic ? accumulator + 1 : accumulator,
-          0
-        ),
-      }),
-      ...(this.opts.enableTasksQueue === true && {
-        backPressure: this.backPressure,
-        backPressureWorkerNodes: this.workerNodes.reduce(
-          (accumulator, _, workerNodeKey) =>
-            this.isWorkerNodeBackPressured(workerNodeKey)
-              ? accumulator + 1
-              : accumulator,
-          0
-        ),
-        maxQueuedTasks: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            accumulator + (workerNode.usage.tasks.maxQueued ?? 0),
-          0
-        ),
-        queuedTasks: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            accumulator + workerNode.usage.tasks.queued,
-          0
-        ),
-        stealingWorkerNodes: this.workerNodes.reduce(
-          (accumulator, _, workerNodeKey) =>
-            this.isWorkerNodeStealing(workerNodeKey)
-              ? accumulator + 1
-              : accumulator,
-          0
-        ),
-        stolenTasks: this.workerNodes.reduce(
-          (accumulator, workerNode) =>
-            accumulator + workerNode.usage.tasks.stolen,
-          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
-              )
+  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()}`
             )
-          ),
-          minimum: round(
-            min(
-              ...this.workerNodes.map(
-                workerNode =>
-                  workerNode.usage.waitTime.minimum ?? Number.POSITIVE_INFINITY
+          )
+        }
+      }
+      // 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.
+   * @param workerNodeKey - The worker node key.
+   */
+  private sendStatisticsMessageToWorker (workerNodeKey: number): void {
+    this.sendToWorker(workerNodeKey, {
+      statistics: {
+        elu:
+          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+            .elu.aggregate ?? false,
+        runTime:
+          this.workerChoiceStrategiesContext?.getTaskStatisticsRequirements()
+            .runTime.aggregate ?? false,
+      },
+    })
+  }
+
+  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.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
-                )
+          }
+          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
               )
-            ),
-            minimum: round(
-              min(
-                ...this.workerNodes.map(
-                  workerNode =>
-                    workerNode.usage.elu.idle.minimum ??
-                    Number.POSITIVE_INFINITY
-                )
+            ) {
+              resolve(true)
+            } else if (
+              responsesReceived.some(
+                message => message.taskFunctionOperationStatus === false
               )
-            ),
-            ...(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
-                )
+            ) {
+              const errorResponse = responsesReceived.find(
+                response => response.taskFunctionOperationStatus === false
               )
-            ),
-            median: round(
-              median(
-                this.workerNodes.map(
-                  workerNode => workerNode.usage.elu.utilization ?? 0
+              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
     }
   }
 
-  /**
-   * Whether the pool is ready or not.
-   * @returns The pool readiness boolean status.
-   */
-  private get ready (): boolean {
-    if (!this.started) {
-      return false
+  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.workerNodes.reduce(
-        (accumulator, workerNode) =>
-          !workerNode.info.dynamic && workerNode.info.ready
-            ? accumulator + 1
-            : accumulator,
-        0
-      ) >= this.minimumNumberOfWorkers
+      this.tasksQueueSize(workerNodeKey) === 0 &&
+      this.workerNodes[workerNodeKey].usage.tasks.executing <
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        this.opts.tasksQueueOptions!.concurrency!
     )
   }
 
   /**
-   * The pool type.
-   *
-   * If it is `'dynamic'`, it provides the `max` property.
+   * 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.
    */
-  protected abstract get type (): PoolType
+  private shallUpdateTaskFunctionWorkerUsage (workerNodeKey: number): boolean {
+    const workerInfo = this.getWorkerInfo(workerNodeKey)
+    return (
+      workerInfo != null &&
+      Array.isArray(workerInfo.taskFunctionsProperties) &&
+      workerInfo.taskFunctionsProperties.length > 2
+    )
+  }
 
   /**
-   * The approximate pool utilization.
-   * @returns The pool utilization.
+   * Starts the minimum number of workers.
+   * @param initWorkerNodeUsage - Whether to initialize the worker node usage or not. @defaultValue false
    */
-  private get utilization (): number {
-    if (this.startTimestamp == null) {
-      return 0
+  private startMinimumNumberOfWorkers (initWorkerNodeUsage = false): void {
+    if (this.minimumNumberOfWorkers === 0) {
+      return
     }
-    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
+    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])
+    }
+    this.startingMinimumNumberOfWorkers = false
+  }
+
+  private readonly stealTask = (
+    sourceWorkerNode: IWorkerNode<Worker, Data>,
+    destinationWorkerNodeKey: number
+  ): Task<Data> | undefined => {
+    const destinationWorkerNode = this.workerNodes[destinationWorkerNodeKey]
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (destinationWorkerNode == null) {
+      return
+    }
+    // Avoid cross and cascade task stealing. Could be smarter by checking stealing/stolen worker ids pair.
+    if (
+      !sourceWorkerNode.info.ready ||
+      sourceWorkerNode.info.stolen ||
+      sourceWorkerNode.info.stealing ||
+      !destinationWorkerNode.info.ready ||
+      destinationWorkerNode.info.stolen ||
+      destinationWorkerNode.info.stealing
+    ) {
+      return
+    }
+    destinationWorkerNode.info.stealing = true
+    sourceWorkerNode.info.stolen = true
+    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+    const stolenTask = sourceWorkerNode.dequeueLastPrioritizedTask()!
+    sourceWorkerNode.info.stolen = false
+    destinationWorkerNode.info.stealing = false
+    this.handleTask(destinationWorkerNodeKey, stolenTask)
+    this.updateTaskStolenStatisticsWorkerUsage(
+      destinationWorkerNodeKey,
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      stolenTask.name!
     )
-    return (totalTasksRunTime + totalTasksWaitTime) / poolTimeCapacity
+    return stolenTask
   }
 
-  /**
-   * The worker type.
-   */
-  protected abstract get worker (): WorkerType
+  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
+      )
+    }
+  }
+
+  private unsetTaskStealing (): void {
+    for (const workerNodeKey of this.workerNodes.keys()) {
+      this.workerNodes[workerNodeKey].off(
+        'idle',
+        this.handleWorkerNodeIdleEvent
+      )
+    }
+  }
+
+  private updateTaskSequentiallyStolenStatisticsWorkerUsage (
+    workerNodeKey: number,
+    taskName?: string,
+    previousTaskName?: string
+  ): void {
+    const workerNode = this.workerNodes[workerNodeKey]
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+    if (workerNode?.usage != null && taskName != null) {
+      ++workerNode.usage.tasks.sequentiallyStolen
+    }
+    if (
+      taskName != null &&
+      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
+      workerNode.getTaskFunctionWorkerUsage(taskName) != null
+    ) {
+      const taskFunctionWorkerUsage =
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        workerNode.getTaskFunctionWorkerUsage(taskName)!
+      if (
+        taskFunctionWorkerUsage.tasks.sequentiallyStolen === 0 ||
+        (previousTaskName != null &&
+          previousTaskName === taskName &&
+          taskFunctionWorkerUsage.tasks.sequentiallyStolen > 0)
+      ) {
+        ++taskFunctionWorkerUsage.tasks.sequentiallyStolen
+      } else if (taskFunctionWorkerUsage.tasks.sequentiallyStolen > 0) {
+        taskFunctionWorkerUsage.tasks.sequentiallyStolen = 0
+      }
+    }
+  }
+
+  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
+    }
+    if (
+      this.shallUpdateTaskFunctionWorkerUsage(workerNodeKey) &&
+      workerNode.getTaskFunctionWorkerUsage(taskName) != null
+    ) {
+      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+      ++workerNode.getTaskFunctionWorkerUsage(taskName)!.tasks.stolen
+    }
+  }
+
+  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)
+    }
+  }
 }
index 014e3e8057333e97395bfc0b1a9ecea3efad7db2..067ef3b82cb1ed7646d548f2842a236653c89241 100644 (file)
@@ -16,6 +16,21 @@ export class DynamicClusterPool<
   Data = unknown,
   Response = unknown
 > extends FixedClusterPool<Data, Response> {
+  /** @inheritDoc */
+  protected override get backPressure (): boolean {
+    return this.full && this.internalBackPressure()
+  }
+
+  /** @inheritDoc */
+  protected override get busy (): boolean {
+    return this.full && this.internalBusy()
+  }
+
+  /** @inheritDoc */
+  protected override get type (): PoolType {
+    return PoolTypes.dynamic
+  }
+
   /**
    * Whether the pool empty event has been emitted or not
    */
@@ -26,6 +41,28 @@ export class DynamicClusterPool<
    */
   private fullEventEmitted: boolean
 
+  /**
+   * Whether the pool is empty or not.
+   * @returns The pool emptiness boolean status.
+   */
+  private get empty (): boolean {
+    return (
+      this.minimumNumberOfWorkers === 0 &&
+      this.workerNodes.length === this.minimumNumberOfWorkers
+    )
+  }
+
+  /**
+   * Whether the pool is full or not.
+   * @returns The pool fullness boolean status.
+   */
+  private get full (): boolean {
+    return (
+      this.workerNodes.length >=
+      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
+    )
+  }
+
   /**
    * Constructs a new poolifier dynamic cluster pool.
    * @param min - Minimum number of workers which are always active.
@@ -79,41 +116,4 @@ export class DynamicClusterPool<
   protected override shallCreateDynamicWorker (): boolean {
     return (!this.full && this.internalBusy()) || this.empty
   }
-
-  /** @inheritDoc */
-  protected override get backPressure (): boolean {
-    return this.full && this.internalBackPressure()
-  }
-
-  /** @inheritDoc */
-  protected override get busy (): boolean {
-    return this.full && this.internalBusy()
-  }
-
-  /**
-   * Whether the pool is empty or not.
-   * @returns The pool emptiness boolean status.
-   */
-  private get empty (): boolean {
-    return (
-      this.minimumNumberOfWorkers === 0 &&
-      this.workerNodes.length === this.minimumNumberOfWorkers
-    )
-  }
-
-  /**
-   * Whether the pool is full or not.
-   * @returns The pool fullness boolean status.
-   */
-  private get full (): boolean {
-    return (
-      this.workerNodes.length >=
-      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
-    )
-  }
-
-  /** @inheritDoc */
-  protected override get type (): PoolType {
-    return PoolTypes.dynamic
-  }
 }
index 17b64c30c1e9af745d3a483356b2e8518d42574b..0023aeb8d184c56aa6d7d1bfb83a35dfff1e97c1 100644 (file)
@@ -22,6 +22,26 @@ export class FixedClusterPool<
   Data = unknown,
   Response = unknown
 > extends AbstractPool<Worker, Data, Response> {
+  /** @inheritDoc */
+  protected get backPressure (): boolean {
+    return this.internalBackPressure()
+  }
+
+  /** @inheritDoc */
+  protected get busy (): boolean {
+    return this.internalBusy()
+  }
+
+  /** @inheritDoc */
+  protected get type (): PoolType {
+    return PoolTypes.fixed
+  }
+
+  /** @inheritDoc */
+  protected get worker (): WorkerType {
+    return WorkerTypes.cluster
+  }
+
   /**
    * Constructs a new poolifier fixed cluster pool.
    * @param numberOfWorkers - Number of workers for this pool.
@@ -104,24 +124,4 @@ export class FixedClusterPool<
   protected shallCreateDynamicWorker (): boolean {
     return false
   }
-
-  /** @inheritDoc */
-  protected get backPressure (): boolean {
-    return this.internalBackPressure()
-  }
-
-  /** @inheritDoc */
-  protected get busy (): boolean {
-    return this.internalBusy()
-  }
-
-  /** @inheritDoc */
-  protected get type (): PoolType {
-    return PoolTypes.fixed
-  }
-
-  /** @inheritDoc */
-  protected get worker (): WorkerType {
-    return WorkerTypes.cluster
-  }
 }
index 74ceb0e34428858708ce0f5e6a9de8df1855ec35..2318c72e13645e6a5b7e9b41cf765d635a843839 100644 (file)
@@ -72,6 +72,146 @@ export const PoolEvents: Readonly<{
   taskError: 'taskError',
 } as const)
 
+/**
+ * Contract definition for a poolifier pool.
+ * @typeParam Worker - Type of worker which manages this pool.
+ * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
+ * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
+ */
+export interface IPool<
+  Worker extends IWorker,
+  Data = unknown,
+  Response = unknown
+> {
+  /**
+   * 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>
+  /**
+   * Terminates all workers in this pool.
+   */
+  readonly destroy: () => Promise<void>
+  /**
+   * Pool event emitter integrated with async resource.
+   * The async tracking tooling identifier is `poolifier:<PoolType>-<WorkerType>-pool`.
+   *
+   * Events that can currently be listened to:
+   *
+   * - `'ready'`: Emitted when the number of workers created in the pool has reached the minimum size expected and are ready. If the pool is dynamic with a minimum number of workers set to zero, this event is emitted when the pool is started.
+   * - `'busy'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are executing concurrently their tasks quota.
+   * - `'busyEnd'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are no longer executing concurrently their tasks quota.
+   * - `'full'`: Emitted when the pool is dynamic and the number of workers created has reached the maximum size expected.
+   * - `'fullEnd'`: Emitted when the pool is dynamic and the number of workers created has no longer reached the maximum size expected.
+   * - `'empty'`: Emitted when the pool is dynamic with a minimum number of workers set to zero and the number of workers has reached the minimum size expected.
+   * - `'destroy'`: Emitted when the pool is destroyed.
+   * - `'error'`: Emitted when an uncaught error occurs.
+   * - `'taskError'`: Emitted when an error occurs while executing a task.
+   * - `'backPressure'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are back pressured (i.e. their tasks queue is full: queue size \>= maximum queue size).
+   * - `'backPressureEnd'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are no longer back pressured (i.e. their tasks queue is no longer 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.
+   * @param name - The optional name of the task function to execute. If not specified, the default task function will be executed.
+   * @param transferList - An optional array of transferable objects to transfer ownership of. Ownership of the transferred objects is given to the chosen pool's worker_threads worker and they should not be used in the main thread afterwards.
+   * @returns Promise with a task function response that will be fulfilled when the task is completed.
+   */
+  readonly execute: (
+    data?: Data,
+    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.
+   * @param name - The optional name of the task function to execute. If not specified, the default task function will be executed.
+   * @param transferList - An optional array of transferable objects to transfer ownership of. Ownership of the transferred objects is given to the chosen pool's worker_threads worker and they should not be used in the main thread afterwards.
+   * @returns Promise with an array of task function responses that will be fulfilled when the tasks are completed.
+   */
+  readonly mapExecute: (
+    data: Iterable<Data>,
+    name?: string,
+    transferList?: readonly TransferListItem[]
+  ) => Promise<Response[]>
+  /**
+   * 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>
+  /**
+   * 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.
+   * @param workerChoiceStrategyOptions - The worker choice strategy options.
+   */
+  readonly setWorkerChoiceStrategy: (
+    workerChoiceStrategy: WorkerChoiceStrategy,
+    workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions
+  ) => void
+  /**
+   * Sets the worker choice strategy options in this pool.
+   * @param workerChoiceStrategyOptions - The worker choice strategy options.
+   * @returns `true` if the worker choice strategy options were set, `false` otherwise.
+   */
+  readonly setWorkerChoiceStrategyOptions: (
+    workerChoiceStrategyOptions: WorkerChoiceStrategyOptions
+  ) => boolean
+  /**
+   * Starts the minimum number of workers in this pool.
+   */
+  readonly start: () => void
+  /**
+   * Pool worker nodes.
+   * @internal
+   */
+  readonly workerNodes: IWorkerNode<Worker, Data>[]
+}
+
 /**
  * Pool event.
  */
@@ -143,42 +283,6 @@ export interface PoolInfo {
   readonly workerNodes: number
 }
 
-/**
- * Worker node tasks queue options.
- */
-export interface TasksQueueOptions {
-  /**
-   * Maximum number of tasks that can be executed concurrently on a worker node.
-   * @defaultValue 1
-   */
-  readonly concurrency?: number
-  /**
-   * Maximum tasks queue size per worker node flagging it as back pressured.
-   * @defaultValue (pool maximum size)^2
-   */
-  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
-   */
-  readonly tasksStealingOnBackPressure?: boolean
-  /**
-   * Ratio of worker nodes that can steal tasks from another worker node.
-   * @defaultValue 0.6
-   */
-  readonly tasksStealingRatio?: number
-  /**
-   * Whether to enable task stealing on idle.
-   * @defaultValue true
-   */
-  readonly taskStealing?: boolean
-}
-
 /**
  * Options for a poolifier pool.
  * @typeParam Worker - Type of worker.
@@ -254,141 +358,37 @@ export interface PoolOptions<Worker extends IWorker> {
 }
 
 /**
- * Contract definition for a poolifier pool.
- * @typeParam Worker - Type of worker which manages this pool.
- * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
- * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
+ * Worker node tasks queue options.
  */
-export interface IPool<
-  Worker extends IWorker,
-  Data = unknown,
-  Response = unknown
-> {
-  /**
-   * 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>
-  /**
-   * Terminates all workers in this pool.
-   */
-  readonly destroy: () => Promise<void>
-  /**
-   * Pool event emitter integrated with async resource.
-   * The async tracking tooling identifier is `poolifier:<PoolType>-<WorkerType>-pool`.
-   *
-   * Events that can currently be listened to:
-   *
-   * - `'ready'`: Emitted when the number of workers created in the pool has reached the minimum size expected and are ready. If the pool is dynamic with a minimum number of workers set to zero, this event is emitted when the pool is started.
-   * - `'busy'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are executing concurrently their tasks quota.
-   * - `'busyEnd'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are no longer executing concurrently their tasks quota.
-   * - `'full'`: Emitted when the pool is dynamic and the number of workers created has reached the maximum size expected.
-   * - `'fullEnd'`: Emitted when the pool is dynamic and the number of workers created has no longer reached the maximum size expected.
-   * - `'empty'`: Emitted when the pool is dynamic with a minimum number of workers set to zero and the number of workers has reached the minimum size expected.
-   * - `'destroy'`: Emitted when the pool is destroyed.
-   * - `'error'`: Emitted when an uncaught error occurs.
-   * - `'taskError'`: Emitted when an error occurs while executing a task.
-   * - `'backPressure'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are back pressured (i.e. their tasks queue is full: queue size \>= maximum queue size).
-   * - `'backPressureEnd'`: Emitted when the number of workers created in the pool has reached the maximum size expected and are no longer back pressured (i.e. their tasks queue is no longer 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.
-   * @param name - The optional name of the task function to execute. If not specified, the default task function will be executed.
-   * @param transferList - An optional array of transferable objects to transfer ownership of. Ownership of the transferred objects is given to the chosen pool's worker_threads worker and they should not be used in the main thread afterwards.
-   * @returns Promise with a task function response that will be fulfilled when the task is completed.
-   */
-  readonly execute: (
-    data?: Data,
-    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.
-   * @param name - The optional name of the task function to execute. If not specified, the default task function will be executed.
-   * @param transferList - An optional array of transferable objects to transfer ownership of. Ownership of the transferred objects is given to the chosen pool's worker_threads worker and they should not be used in the main thread afterwards.
-   * @returns Promise with an array of task function responses that will be fulfilled when the tasks are completed.
-   */
-  readonly mapExecute: (
-    data: Iterable<Data>,
-    name?: string,
-    transferList?: readonly TransferListItem[]
-  ) => Promise<Response[]>
-  /**
-   * 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>
+export interface TasksQueueOptions {
   /**
-   * 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.
+   * Maximum number of tasks that can be executed concurrently on a worker node.
+   * @defaultValue 1
    */
-  readonly setDefaultTaskFunction: (name: string) => Promise<boolean>
+  readonly concurrency?: number
   /**
-   * Sets the worker node tasks queue options in this pool.
-   * @param tasksQueueOptions - The worker node tasks queue options.
+   * Maximum tasks queue size per worker node flagging it as back pressured.
+   * @defaultValue (pool maximum size)^2
    */
-  readonly setTasksQueueOptions: (tasksQueueOptions: TasksQueueOptions) => void
+  readonly size?: number
   /**
-   * Sets the default worker choice strategy in this pool.
-   * @param workerChoiceStrategy - The default worker choice strategy.
-   * @param workerChoiceStrategyOptions - The worker choice strategy options.
+   * Queued tasks finished timeout in milliseconds at worker node termination.
+   * @defaultValue 2000
    */
-  readonly setWorkerChoiceStrategy: (
-    workerChoiceStrategy: WorkerChoiceStrategy,
-    workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions
-  ) => void
+  readonly tasksFinishedTimeout?: number
   /**
-   * Sets the worker choice strategy options in this pool.
-   * @param workerChoiceStrategyOptions - The worker choice strategy options.
-   * @returns `true` if the worker choice strategy options were set, `false` otherwise.
+   * Whether to enable tasks stealing under back pressure.
+   * @defaultValue true
    */
-  readonly setWorkerChoiceStrategyOptions: (
-    workerChoiceStrategyOptions: WorkerChoiceStrategyOptions
-  ) => boolean
+  readonly tasksStealingOnBackPressure?: boolean
   /**
-   * Starts the minimum number of workers in this pool.
+   * Ratio of worker nodes that can steal tasks from another worker node.
+   * @defaultValue 0.6
    */
-  readonly start: () => void
+  readonly tasksStealingRatio?: number
   /**
-   * Pool worker nodes.
-   * @internal
+   * Whether to enable task stealing on idle.
+   * @defaultValue true
    */
-  readonly workerNodes: IWorkerNode<Worker, Data>[]
+  readonly taskStealing?: boolean
 }
index 9d1a62e04fe6d7d6f1458022f4e635b095fb3010..70a00342dd4f908f0e88cdfffb1702250a5005ef 100644 (file)
@@ -24,16 +24,6 @@ export abstract class AbstractWorkerChoiceStrategy<
   Data = unknown,
   Response = unknown
 > implements IWorkerChoiceStrategy {
-  /**
-   * The next worker node key.
-   */
-  protected nextWorkerNodeKey: number | undefined = 0
-
-  /**
-   * The previous worker node key.
-   */
-  protected previousWorkerNodeKey = 0
-
   /** @inheritDoc */
   public readonly strategyPolicy: StrategyPolicy = {
     dynamicWorkerReady: true,
@@ -48,6 +38,16 @@ export abstract class AbstractWorkerChoiceStrategy<
       waitTime: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
     })
 
+  /**
+   * The next worker node key.
+   */
+  protected nextWorkerNodeKey: number | undefined = 0
+
+  /**
+   * The previous worker node key.
+   */
+  protected previousWorkerNodeKey = 0
+
   /**
    * Constructs a worker choice strategy bound to the pool.
    * @param pool - The pool instance.
@@ -61,6 +61,27 @@ export abstract class AbstractWorkerChoiceStrategy<
     this.setOptions(this.opts)
   }
 
+  /** @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
+
   /**
    * Check the next worker node key.
    */
@@ -157,25 +178,4 @@ export abstract class AbstractWorkerChoiceStrategy<
       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 fc0dcc3baebdd1b373fafc4c8da457c8d50d5b3b..44f39b5784418f929b6f11964cd10d6517b54d94 100644 (file)
@@ -52,6 +52,35 @@ export class FairShareWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
+  /** @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
+  }
+
   /**
    * Computes the worker node key virtual task end timestamp.
    * @param workerNodeKey - The worker node key.
@@ -112,33 +141,4 @@ 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 fbc79718471d77748f3337ab2511e27d41bec974..5a3310e87b2f7c761d194376aef024fbf5601517 100644 (file)
@@ -22,11 +22,26 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
   >
   extends AbstractWorkerChoiceStrategy<Worker, Data, Response>
   implements IWorkerChoiceStrategy {
+  /** @inheritDoc */
+  public override readonly taskStatisticsRequirements: TaskStatisticsRequirements =
+    Object.freeze({
+      elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
+      runTime: {
+        aggregate: true,
+        average: true,
+        median: false,
+      },
+      waitTime: {
+        aggregate: true,
+        average: true,
+        median: false,
+      },
+    })
+
   /**
    * Round id.
    */
   private roundId = 0
-
   /**
    * Round weights.
    */
@@ -39,21 +54,6 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
    * Worker node virtual execution time.
    */
   private workerNodeVirtualTaskExecutionTime = 0
-  /** @inheritDoc */
-  public override readonly taskStatisticsRequirements: TaskStatisticsRequirements =
-    Object.freeze({
-      elu: DEFAULT_MEASUREMENT_STATISTICS_REQUIREMENTS,
-      runTime: {
-        aggregate: true,
-        average: true,
-        median: false,
-      },
-      waitTime: {
-        aggregate: true,
-        average: true,
-        median: false,
-      },
-    })
 
   /** @inheritDoc */
   public constructor (
@@ -65,34 +65,6 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
     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)
-      ),
-    ]
-  }
-
-  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 choose (): number | undefined {
     for (
@@ -176,4 +148,32 @@ export class InterleavedWeightedRoundRobinWorkerChoiceStrategy<
   public update (): boolean {
     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)
+      ),
+    ]
+  }
+
+  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
+    }
+  }
 }
index b6ad7075b10cc085f54e51fd8f7baa374cd834f5..ef61c72a09af4c19cdca5e5a9193f3d796a15843 100644 (file)
@@ -47,21 +47,6 @@ export class LeastBusyWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
-  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 choose (): number | undefined {
     this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey)
@@ -83,4 +68,19 @@ export class LeastBusyWorkerChoiceStrategy<
   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
+    )
+  }
 }
index 37193a1cba4a1ae9eb23d1b439ac710a3b5d2b92..7ac70513a26840ae6a11cf7e1fd04dddb598e9c0 100644 (file)
@@ -43,19 +43,6 @@ export class LeastEluWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
-  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 choose (): number | undefined {
     this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey)
@@ -77,4 +64,17 @@ export class LeastEluWorkerChoiceStrategy<
   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
+    )
+  }
 }
index 067e1238fbbba9ff9541345ff57ff6c09a7cdeeb..5cdcb3b725cc5295a10be54692cfc9858c09d0c8 100644 (file)
@@ -28,20 +28,6 @@ export class LeastUsedWorkerChoiceStrategy<
     super(pool, opts)
   }
 
-  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 choose (): number | undefined {
     this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey)
@@ -63,4 +49,18 @@ export class LeastUsedWorkerChoiceStrategy<
   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
+    )
+  }
 }
index 39cb3a0803c00183aebd8e67b78b6084655a3ac8..a70b72e92506b9fecf3b5154bbb4294edfc0dffc 100644 (file)
@@ -28,14 +28,6 @@ export class RoundRobinWorkerChoiceStrategy<
     super(pool, opts)
   }
 
-  private roundRobinNextWorkerNodeKey (): number | undefined {
-    this.nextWorkerNodeKey =
-      this.nextWorkerNodeKey === this.pool.workerNodes.length - 1
-        ? 0
-        : (this.nextWorkerNodeKey ?? this.previousWorkerNodeKey) + 1
-    return this.nextWorkerNodeKey
-  }
-
   /** @inheritDoc */
   public choose (): number | undefined {
     const chosenWorkerNodeKey = this.nextWorkerNodeKey
@@ -76,4 +68,12 @@ export class RoundRobinWorkerChoiceStrategy<
   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
+  }
 }
index 236a3adfff3594bc6b30aa2692d467845082d0a4..393b7098131b87999b6e757d6e3664e3ddc604a4 100644 (file)
@@ -60,49 +60,62 @@ export const Measurements: Readonly<{
 } as const)
 
 /**
- * Measurement.
- */
-export type Measurement = keyof typeof Measurements
-
-/**
- * Measurement options.
+ * Worker choice strategy interface.
+ * @internal
  */
-export interface MeasurementOptions {
+export interface IWorkerChoiceStrategy {
   /**
-   * Set measurement median.
+   * Chooses a worker node in the pool and returns its key.
+   * If no worker nodes are not eligible, `undefined` is returned.
+   * If `undefined` is returned, the caller retry.
+   * @returns The worker node key or `undefined`.
    */
-  readonly median: boolean
-}
-
-/**
- * Worker choice strategy options.
- */
-export interface WorkerChoiceStrategyOptions {
+  readonly choose: () => number | undefined
   /**
-   * Event loop utilization options.
-   * @defaultValue \{ median: false \}
+   * Removes the worker node key from strategy internals.
+   * @param workerNodeKey - The worker node key.
+   * @returns `true` if the worker node key is removed, `false` otherwise.
    */
-  readonly elu?: MeasurementOptions
+  readonly remove: (workerNodeKey: number) => boolean
   /**
-   * Measurement to use in worker choice strategy supporting it.
+   * Resets strategy internals.
+   * @returns `true` if the reset is successful, `false` otherwise.
    */
-  readonly measurement?: Measurement
+  readonly reset: () => boolean
   /**
-   * Runtime options.
-   * @defaultValue \{ median: false \}
+   * Sets the worker choice strategy options.
+   * @param opts - The worker choice strategy options.
    */
-  readonly runTime?: MeasurementOptions
+  readonly setOptions: (opts: undefined | WorkerChoiceStrategyOptions) => void
   /**
-   * Wait time options.
-   * @defaultValue \{ median: false \}
+   * Strategy policy.
    */
-  readonly waitTime?: MeasurementOptions
+  readonly strategyPolicy: StrategyPolicy
   /**
-   * Worker weights to use for weighted round robin worker selection strategies.
-   * A weight is tasks maximum execution time in milliseconds for a worker node.
-   * @defaultValue Weights computed automatically given the CPU performance.
+   * Tasks statistics requirements.
    */
-  weights?: Record<number, number>
+  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
+}
+
+/**
+ * Measurement.
+ */
+export type Measurement = keyof typeof Measurements
+
+/**
+ * Measurement options.
+ */
+export interface MeasurementOptions {
+  /**
+   * Set measurement median.
+   */
+  readonly median: boolean
 }
 
 /**
@@ -124,25 +137,6 @@ export interface MeasurementStatisticsRequirements {
   median: boolean
 }
 
-/**
- * Pool worker node worker usage statistics requirements.
- * @internal
- */
-export interface TaskStatisticsRequirements {
-  /**
-   * Tasks event loop utilization requirements.
-   */
-  readonly elu: MeasurementStatisticsRequirements
-  /**
-   * Tasks runtime requirements.
-   */
-  readonly runTime: MeasurementStatisticsRequirements
-  /**
-   * Tasks wait time requirements.
-   */
-  readonly waitTime: MeasurementStatisticsRequirements
-}
-
 /**
  * Strategy policy.
  * @internal
@@ -159,45 +153,51 @@ export interface StrategyPolicy {
 }
 
 /**
- * Worker choice strategy interface.
+ * Pool worker node worker usage statistics requirements.
  * @internal
  */
-export interface IWorkerChoiceStrategy {
+export interface TaskStatisticsRequirements {
   /**
-   * Chooses a worker node in the pool and returns its key.
-   * If no worker nodes are not eligible, `undefined` is returned.
-   * If `undefined` is returned, the caller retry.
-   * @returns The worker node key or `undefined`.
+   * Tasks event loop utilization requirements.
    */
-  readonly choose: () => number | undefined
+  readonly elu: MeasurementStatisticsRequirements
   /**
-   * Removes the worker node key from strategy internals.
-   * @param workerNodeKey - The worker node key.
-   * @returns `true` if the worker node key is removed, `false` otherwise.
+   * Tasks runtime requirements.
    */
-  readonly remove: (workerNodeKey: number) => boolean
+  readonly runTime: MeasurementStatisticsRequirements
   /**
-   * Resets strategy internals.
-   * @returns `true` if the reset is successful, `false` otherwise.
+   * Tasks wait time requirements.
    */
-  readonly reset: () => boolean
+  readonly waitTime: MeasurementStatisticsRequirements
+}
+
+/**
+ * Worker choice strategy options.
+ */
+export interface WorkerChoiceStrategyOptions {
   /**
-   * Sets the worker choice strategy options.
-   * @param opts - The worker choice strategy options.
+   * Event loop utilization options.
+   * @defaultValue \{ median: false \}
    */
-  readonly setOptions: (opts: undefined | WorkerChoiceStrategyOptions) => void
+  readonly elu?: MeasurementOptions
   /**
-   * Strategy policy.
+   * Measurement to use in worker choice strategy supporting it.
    */
-  readonly strategyPolicy: StrategyPolicy
+  readonly measurement?: Measurement
   /**
-   * Tasks statistics requirements.
+   * Runtime options.
+   * @defaultValue \{ median: false \}
    */
-  readonly taskStatisticsRequirements: TaskStatisticsRequirements
+  readonly runTime?: MeasurementOptions
   /**
-   * 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.
+   * Wait time options.
+   * @defaultValue \{ median: false \}
    */
-  readonly update: (workerNodeKey: number) => boolean
+  readonly waitTime?: 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.
+   * @defaultValue Weights computed automatically given the CPU performance.
+   */
+  weights?: Record<number, number>
 }
index a62103a942b974d228a486995186a03e2db483f9..edc54cd5b5efe72c50724d89fdcdbb3850e9ba3b 100644 (file)
@@ -23,11 +23,6 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
   >
   extends AbstractWorkerChoiceStrategy<Worker, Data, Response>
   implements IWorkerChoiceStrategy {
-  /**
-   * Worker node virtual execution time.
-   */
-  private workerNodeVirtualTaskExecutionTime = 0
-
   /** @inheritDoc */
   public override readonly taskStatisticsRequirements: TaskStatisticsRequirements =
     Object.freeze({
@@ -44,6 +39,11 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
       },
     })
 
+  /**
+   * Worker node virtual execution time.
+   */
+  private workerNodeVirtualTaskExecutionTime = 0
+
   /** @inheritDoc */
   public constructor (
     pool: IPool<Worker, Data, Response>,
@@ -53,28 +53,6 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
     this.setTaskStatisticsRequirements(this.opts)
   }
 
-  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 choose (): number | undefined {
     this.setPreviousWorkerNodeKey(this.nextWorkerNodeKey)
@@ -115,4 +93,26 @@ export class WeightedRoundRobinWorkerChoiceStrategy<
   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
+  }
 }
index a1ccb4e96277c21895a868e231c0941a030a6371..0e5085becac09f2226744a82874a204b5196a50d 100644 (file)
@@ -28,6 +28,11 @@ export class WorkerChoiceStrategiesContext<
   Data = unknown,
   Response = unknown
 > {
+  /**
+   * The number of worker choice strategies execution retries.
+   */
+  public retriesCount: number
+
   /**
    * The default worker choice strategy in the context.
    */
@@ -56,11 +61,6 @@ export class WorkerChoiceStrategiesContext<
    */
   private workerChoiceStrategiesTaskStatisticsRequirements: TaskStatisticsRequirements
 
-  /**
-   * The number of worker choice strategies execution retries.
-   */
-  public retriesCount: number
-
   /**
    * Worker choice strategies context constructor.
    * @param pool - The pool instance.
@@ -97,69 +97,6 @@ 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
-  }
-
-  /**
-   * Executes the given worker choice strategy.
-   * @param workerChoiceStrategy - The worker choice strategy.
-   * @returns The key of the worker node.
-   * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
-   */
-  private executeStrategy (workerChoiceStrategy: IWorkerChoiceStrategy): number {
-    let workerNodeKey: number | undefined
-    let chooseCount = 0
-    let retriesCount = 0
-    do {
-      workerNodeKey = workerChoiceStrategy.choose()
-      if (workerNodeKey == null && chooseCount > 0) {
-        ++retriesCount
-        ++this.retriesCount
-      }
-      ++chooseCount
-    } while (workerNodeKey == null && retriesCount < this.retries)
-    if (workerNodeKey == null) {
-      throw new Error(
-        `Worker node key chosen is null or undefined after ${retriesCount.toString()} retries`
-      )
-    }
-    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
@@ -268,4 +205,67 @@ export class WorkerChoiceStrategiesContext<
       ([_, workerChoiceStrategy]) => workerChoiceStrategy.update(workerNodeKey)
     ).every(r => r)
   }
+
+  /**
+   * 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
+  }
+
+  /**
+   * Executes the given worker choice strategy.
+   * @param workerChoiceStrategy - The worker choice strategy.
+   * @returns The key of the worker node.
+   * @throws {@link https://nodejs.org/api/errors.html#class-error} If after computed retries the worker node key is null or undefined.
+   */
+  private executeStrategy (workerChoiceStrategy: IWorkerChoiceStrategy): number {
+    let workerNodeKey: number | undefined
+    let chooseCount = 0
+    let retriesCount = 0
+    do {
+      workerNodeKey = workerChoiceStrategy.choose()
+      if (workerNodeKey == null && chooseCount > 0) {
+        ++retriesCount
+        ++this.retriesCount
+      }
+      ++chooseCount
+    } while (workerNodeKey == null && retriesCount < this.retries)
+    if (workerNodeKey == null) {
+      throw new Error(
+        `Worker node key chosen is null or undefined after ${retriesCount.toString()} retries`
+      )
+    }
+    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)
+  }
 }
index fdf1486eb7c2ae29416ad19ccbad53dbab37dac6..b64f79a60b1268b4016ad180e34773f3c363c66e 100644 (file)
@@ -16,6 +16,21 @@ export class DynamicThreadPool<
   Data = unknown,
   Response = unknown
 > extends FixedThreadPool<Data, Response> {
+  /** @inheritDoc */
+  protected override get backPressure (): boolean {
+    return this.full && this.internalBackPressure()
+  }
+
+  /** @inheritDoc */
+  protected override get busy (): boolean {
+    return this.full && this.internalBusy()
+  }
+
+  /** @inheritDoc */
+  protected override get type (): PoolType {
+    return PoolTypes.dynamic
+  }
+
   /**
    * Whether the pool empty event has been emitted or not
    */
@@ -26,6 +41,28 @@ export class DynamicThreadPool<
    */
   private fullEventEmitted: boolean
 
+  /**
+   * Whether the pool is empty or not.
+   * @returns The pool emptiness boolean status.
+   */
+  private get empty (): boolean {
+    return (
+      this.minimumNumberOfWorkers === 0 &&
+      this.workerNodes.length === this.minimumNumberOfWorkers
+    )
+  }
+
+  /**
+   * Whether the pool is full or not.
+   * @returns The pool fullness boolean status.
+   */
+  private get full (): boolean {
+    return (
+      this.workerNodes.length >=
+      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
+    )
+  }
+
   /**
    * Constructs a new poolifier dynamic thread pool.
    * @param min - Minimum number of threads which are always active.
@@ -79,41 +116,4 @@ export class DynamicThreadPool<
   protected override shallCreateDynamicWorker (): boolean {
     return (!this.full && this.internalBusy()) || this.empty
   }
-
-  /** @inheritDoc */
-  protected override get backPressure (): boolean {
-    return this.full && this.internalBackPressure()
-  }
-
-  /** @inheritDoc */
-  protected override get busy (): boolean {
-    return this.full && this.internalBusy()
-  }
-
-  /**
-   * Whether the pool is empty or not.
-   * @returns The pool emptiness boolean status.
-   */
-  private get empty (): boolean {
-    return (
-      this.minimumNumberOfWorkers === 0 &&
-      this.workerNodes.length === this.minimumNumberOfWorkers
-    )
-  }
-
-  /**
-   * Whether the pool is full or not.
-   * @returns The pool fullness boolean status.
-   */
-  private get full (): boolean {
-    return (
-      this.workerNodes.length >=
-      (this.maximumNumberOfWorkers ?? this.minimumNumberOfWorkers)
-    )
-  }
-
-  /** @inheritDoc */
-  protected override get type (): PoolType {
-    return PoolTypes.dynamic
-  }
 }
index 612a9edfd1ff345fda1a1894c4eb10876098bd86..90b7661c0bb3a76c2d2150b29d59164a779a1c3d 100644 (file)
@@ -26,6 +26,26 @@ export class FixedThreadPool<
   Data = unknown,
   Response = unknown
 > extends AbstractPool<Worker, Data, Response> {
+  /** @inheritDoc */
+  protected get backPressure (): boolean {
+    return this.internalBackPressure()
+  }
+
+  /** @inheritDoc */
+  protected get busy (): boolean {
+    return this.internalBusy()
+  }
+
+  /** @inheritDoc */
+  protected get type (): PoolType {
+    return PoolTypes.fixed
+  }
+
+  /** @inheritDoc */
+  protected get worker (): WorkerType {
+    return WorkerTypes.thread
+  }
+
   /**
    * Constructs a new poolifier fixed thread pool.
    * @param numberOfThreads - Number of threads for this pool.
@@ -124,24 +144,4 @@ export class FixedThreadPool<
   protected shallCreateDynamicWorker (): boolean {
     return false
   }
-
-  /** @inheritDoc */
-  protected get backPressure (): boolean {
-    return this.internalBackPressure()
-  }
-
-  /** @inheritDoc */
-  protected get busy (): boolean {
-    return this.internalBusy()
-  }
-
-  /** @inheritDoc */
-  protected get type (): PoolType {
-    return PoolTypes.fixed
-  }
-
-  /** @inheritDoc */
-  protected get worker (): WorkerType {
-    return WorkerTypes.thread
-  }
 }
index 73160a25b179dec8127f0e089635b2bf2005c6ca..93ecb0730c39077b83a3b4ad22a375e1d92c846b 100644 (file)
@@ -33,9 +33,6 @@ import {
 export class WorkerNode<Worker extends IWorker, Data = unknown>
   extends EventEmitter
   implements IWorkerNode<Worker, Data> {
-  private setBackPressureFlag: boolean
-  private readonly taskFunctionsUsage: Map<string, WorkerUsage>
-  private readonly tasksQueue: PriorityQueue<Task<Data>>
   /** @inheritdoc */
   public readonly info: WorkerInfo
   /** @inheritdoc */
@@ -48,6 +45,9 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
   public usage: WorkerUsage
   /** @inheritdoc */
   public readonly worker: Worker
+  private setBackPressureFlag: boolean
+  private readonly taskFunctionsUsage: Map<string, WorkerUsage>
+  private readonly tasksQueue: PriorityQueue<Task<Data>>
 
   /**
    * Constructs a new worker node.
@@ -77,120 +77,6 @@ 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
-    }
-  }
-
-  /**
-   * Whether the worker node is back pressured or not.
-   * @returns `true` if the worker node is back pressured, `false` otherwise.
-   */
-  private hasBackPressure (): boolean {
-    return this.tasksQueue.size >= this.tasksQueueBackPressureSize
-  }
-
-  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 clearTasksQueue (): void {
     this.tasksQueue.clear()
@@ -311,4 +197,118 @@ export class WorkerNode<Worker extends IWorker, Data = unknown>
     }
     await waitWorkerExit
   }
+
+  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
+    }
+  }
+
+  /**
+   * Whether the worker node is back pressured or not.
+   * @returns `true` if the worker node is back pressured, `false` otherwise.
+   */
+  private hasBackPressure (): boolean {
+    return this.tasksQueue.size >= this.tasksQueueBackPressureSize
+  }
+
+  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),
+      },
+    }
+  }
 }
index ff8aa6201f4ba645723d3d8fcdd095e4bf5ff53e..ff3a5c828b022102671ee2cbcca7a2a1e9b331e9 100644 (file)
@@ -5,53 +5,63 @@ import type { CircularBuffer } from '../circular-buffer.js'
 import type { Task, TaskFunctionProperties } from '../utility-types.js'
 
 /**
- * Callback invoked when the worker has started successfully.
+ * Callback invoked if the worker raised an error.
  * @typeParam Worker - Type of worker.
  */
-export type OnlineHandler<Worker extends IWorker> = (this: Worker) => void
+export type ErrorHandler<Worker extends IWorker> = (
+  this: Worker,
+  error: Error
+) => void
 
 /**
- * Callback invoked if the worker has received a message.
+ * Worker event handler.
  * @typeParam Worker - Type of worker.
  */
-export type MessageHandler<Worker extends IWorker> = (
-  this: Worker,
-  message: unknown
-) => void
+export type EventHandler<Worker extends IWorker> =
+  | ErrorHandler<Worker>
+  | ExitHandler<Worker>
+  | MessageHandler<Worker>
+  | OnlineHandler<Worker>
 
 /**
- * Callback invoked if the worker raised an error.
+ * Callback invoked when the worker exits successfully.
  * @typeParam Worker - Type of worker.
  */
-export type ErrorHandler<Worker extends IWorker> = (
+export type ExitHandler<Worker extends IWorker> = (
   this: Worker,
-  error: Error
+  exitCode: number
 ) => void
 
 /**
- * Callback invoked when the worker exits successfully.
+ * Callback invoked if the worker has received a message.
  * @typeParam Worker - Type of worker.
  */
-export type ExitHandler<Worker extends IWorker> = (
+export type MessageHandler<Worker extends IWorker> = (
   this: Worker,
-  exitCode: number
+  message: unknown
 ) => void
 
 /**
- * Worker event handler.
+ * Callback invoked when the worker has started successfully.
  * @typeParam Worker - Type of worker.
  */
-export type EventHandler<Worker extends IWorker> =
-  | ErrorHandler<Worker>
-  | ExitHandler<Worker>
-  | MessageHandler<Worker>
-  | OnlineHandler<Worker>
+export type OnlineHandler<Worker extends IWorker> = (this: Worker) => void
 
 /**
  * Measurement history size.
  */
 export const MeasurementHistorySize = 386
 
+/**
+ * Event loop utilization measurement statistics.
+ * @internal
+ */
+export interface EventLoopUtilizationMeasurementStatistics {
+  readonly active: MeasurementStatistics
+  readonly idle: MeasurementStatistics
+  utilization?: number
+}
+
 /**
  * Measurement statistics.
  * @internal
@@ -83,16 +93,6 @@ export interface MeasurementStatistics {
   minimum?: number
 }
 
-/**
- * Event loop utilization measurement statistics.
- * @internal
- */
-export interface EventLoopUtilizationMeasurementStatistics {
-  readonly active: MeasurementStatistics
-  readonly idle: MeasurementStatistics
-  utilization?: number
-}
-
 /**
  * Task statistics.
  * @internal
@@ -137,94 +137,6 @@ export const WorkerTypes: Readonly<{ cluster: 'cluster'; thread: 'thread' }> =
     thread: 'thread',
   } as const)
 
-/**
- * Worker type.
- */
-export type WorkerType = keyof typeof WorkerTypes
-
-/**
- * Worker information.
- * @internal
- */
-export interface WorkerInfo {
-  /**
-   * Back pressure flag.
-   * This flag is set to `true` when worker node tasks queue is back pressured.
-   */
-  backPressure: 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
-  /**
-   * 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.
-   */
-  ready: boolean
-  /**
-   * Stealing flag.
-   * This flag is set to `true` when worker node is stealing one task from another worker node.
-   */
-  stealing: boolean
-  /**
-   * Stolen flag.
-   * This flag is set to `true` when worker node has one task stolen from another worker node.
-   */
-  stolen: boolean
-  /**
-   * Task functions properties.
-   */
-  taskFunctionsProperties?: TaskFunctionProperties[]
-  /**
-   * Worker type.
-   */
-  readonly type: WorkerType
-}
-
-/**
- * Worker usage statistics.
- * @internal
- */
-export interface WorkerUsage {
-  /**
-   * Tasks event loop utilization statistics.
-   */
-  readonly elu: EventLoopUtilizationMeasurementStatistics
-  /**
-   * Tasks runtime statistics.
-   */
-  readonly runTime: MeasurementStatistics
-  /**
-   * Tasks statistics.
-   */
-  readonly tasks: TaskStatistics
-  /**
-   * Tasks wait time statistics.
-   */
-  readonly waitTime: MeasurementStatistics
-}
-
-/**
- * Worker choice strategy data.
- * @internal
- */
-export interface StrategyData {
-  virtualTaskEndTimestamp?: number
-}
-
 /**
  * Worker interface.
  */
@@ -270,18 +182,6 @@ export interface IWorker extends EventEmitter {
   readonly unref?: () => void
 }
 
-/**
- * Worker node options.
- * @internal
- */
-export interface WorkerNodeOptions {
-  env?: Record<string, unknown>
-  tasksQueueBackPressureSize: number | undefined
-  tasksQueueBucketSize: number | undefined
-  tasksQueuePriority: boolean | undefined
-  workerOptions?: WorkerOptions
-}
-
 /**
  * Worker node interface.
  * @typeParam Worker - Type of worker.
@@ -383,6 +283,66 @@ export interface IWorkerNode<Worker extends IWorker, Data = unknown>
   readonly worker: Worker
 }
 
+/**
+ * Worker choice strategy data.
+ * @internal
+ */
+export interface StrategyData {
+  virtualTaskEndTimestamp?: number
+}
+
+/**
+ * Worker information.
+ * @internal
+ */
+export interface WorkerInfo {
+  /**
+   * Back pressure flag.
+   * This flag is set to `true` when worker node tasks queue is back pressured.
+   */
+  backPressure: 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
+  /**
+   * 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.
+   */
+  ready: boolean
+  /**
+   * Stealing flag.
+   * This flag is set to `true` when worker node is stealing one task from another worker node.
+   */
+  stealing: boolean
+  /**
+   * Stolen flag.
+   * This flag is set to `true` when worker node has one task stolen from another worker node.
+   */
+  stolen: boolean
+  /**
+   * Task functions properties.
+   */
+  taskFunctionsProperties?: TaskFunctionProperties[]
+  /**
+   * Worker type.
+   */
+  readonly type: WorkerType
+}
+
 /**
  * Worker node event detail.
  * @internal
@@ -391,3 +351,43 @@ export interface WorkerNodeEventDetail {
   workerId?: number
   workerNodeKey?: number
 }
+
+/**
+ * Worker node options.
+ * @internal
+ */
+export interface WorkerNodeOptions {
+  env?: Record<string, unknown>
+  tasksQueueBackPressureSize: number | undefined
+  tasksQueueBucketSize: number | undefined
+  tasksQueuePriority: boolean | undefined
+  workerOptions?: WorkerOptions
+}
+
+/**
+ * Worker type.
+ */
+export type WorkerType = keyof typeof WorkerTypes
+
+/**
+ * Worker usage statistics.
+ * @internal
+ */
+export interface WorkerUsage {
+  /**
+   * Tasks event loop utilization statistics.
+   */
+  readonly elu: EventLoopUtilizationMeasurementStatistics
+  /**
+   * Tasks runtime statistics.
+   */
+  readonly runTime: MeasurementStatistics
+  /**
+   * Tasks statistics.
+   */
+  readonly tasks: TaskStatistics
+  /**
+   * Tasks wait time statistics.
+   */
+  readonly waitTime: MeasurementStatistics
+}
index 00d4dddab8aa9344af5de73580249b311ba17005..b3090adff68f1bb222d04546b66f1e539d1d3eee 100644 (file)
@@ -10,13 +10,13 @@ import {
  * @internal
  */
 export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
-  protected start!: number
   /** @inheritdoc */
   public readonly capacity: number
   /** @inheritdoc */
   public nodeArray: FixedQueueNode<T>[]
   /** @inheritdoc */
   public size!: number
+  protected start!: number
 
   /**
    * Constructs a fixed queue.
@@ -30,21 +30,6 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
     this.clear()
   }
 
-  /**
-   * 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 clear (): void {
     this.start = 0
@@ -115,4 +100,19 @@ export abstract class AbstractFixedQueue<T> implements IFixedQueue<T> {
       },
     }
   }
+
+  /**
+   * 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 c8cb84f02bd083feb856dce5a2c9de759f307fea..51be4266f6a2695b358db188172dad767a8302a7 100644 (file)
@@ -15,12 +15,78 @@ import {
  * @internal
  */
 export class PriorityQueue<T> {
+  /** The priority queue maximum size. */
+  public maxSize!: number
+
+  /**
+   * 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
+    }
+    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
+    }
+    // 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
+  }
+
   private readonly bucketSize: number
   private head!: PriorityQueueNode<T>
   private priorityEnabled: boolean
   private tail!: PriorityQueueNode<T>
-  /** The priority queue maximum size. */
-  public maxSize!: number
 
   /**
    * Constructs a priority queue.
@@ -45,21 +111,6 @@ export class PriorityQueue<T> {
     this.clear()
   }
 
-  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)
-    }
-    if (nodeArray != null) {
-      fixedQueue.nodeArray = nodeArray
-    }
-    return fixedQueue
-  }
-
   /**
    * Clears the priority queue.
    */
@@ -168,68 +219,18 @@ export class PriorityQueue<T> {
     }
   }
 
-  /**
-   * 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
-    }
-    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
+  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)
     }
-    // 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
+    if (nodeArray != null) {
+      fixedQueue.nodeArray = nodeArray
     }
-    return size
+    return fixedQueue
   }
 }
index fdc3a0568c4fcabd0dc5cf445bd842d94eb3f8b2..0a75e2b36506268ee23130bdf153a97b432251f3 100644 (file)
@@ -6,86 +6,92 @@ import type { WorkerChoiceStrategy } from './pools/selection-strategies/selectio
 import type { KillBehavior } from './worker/worker-options.js'
 
 /**
- * Worker error.
- * @typeParam Data - Type of data sent to the worker triggering an error. This can only be structured-cloneable data.
+ * Message object that is passed between main worker and worker.
+ * @typeParam Data - Type of data sent to the worker or execution response. This can only be structured-cloneable data.
+ * @typeParam ErrorData - Type of data sent to the worker triggering an error. This can only be structured-cloneable data.
+ * @internal
  */
-export interface WorkerError<Data = unknown> {
+export interface MessageValue<Data = unknown, ErrorData = unknown>
+  extends Task<Data> {
   /**
-   * Data triggering the error.
+   * Whether the worker starts or stops its activity check.
    */
-  readonly data?: Data
+  readonly checkActive?: boolean
   /**
-   * Error object.
+   * Kill code.
    */
-  readonly error?: Error
+  readonly kill?: 'failure' | 'success' | KillBehavior | true
   /**
-   * Error message.
+   * Message port.
    */
-  readonly message: string
+  readonly port?: MessagePort
   /**
-   * Task function name triggering the error.
+   * Whether the worker is ready or not.
    */
-  readonly name?: string
+  readonly ready?: boolean
   /**
-   * Error stack trace.
+   * Whether the worker computes the given statistics or not.
    */
-  readonly stack?: string
-}
-
-/**
- * Task performance.
- * @internal
- */
-export interface TaskPerformance {
+  readonly statistics?: WorkerStatistics
   /**
-   * Task event loop utilization.
+   * Task function serialized to string.
    */
-  readonly elu?: EventLoopUtilization
+  readonly taskFunction?: string
   /**
-   * Task name.
+   * Task function operation:
+   * - `'add'` - Add a task function.
+   * - `'remove'` - Remove a task function.
+   * - `'default'` - Set a task function as default.
    */
-  readonly name: string
+  readonly taskFunctionOperation?: 'add' | 'default' | 'remove'
   /**
-   * Task runtime.
+   * Whether the task function operation is successful or not.
    */
-  readonly runTime?: number
+  readonly taskFunctionOperationStatus?: boolean
   /**
-   * Task performance timestamp.
+   * Task function properties.
    */
-  readonly timestamp: number
-}
-
-/**
- * Worker task performance statistics computation settings.
- * @internal
- */
-export interface WorkerStatistics {
+  readonly taskFunctionProperties?: TaskFunctionProperties
   /**
-   * Whether the worker computes the task event loop utilization (ELU) or not.
+   * Task functions properties.
    */
-  readonly elu: boolean
+  readonly taskFunctionsProperties?: TaskFunctionProperties[]
   /**
-   * Whether the worker computes the task runtime or not.
+   * Task performance.
    */
-  readonly runTime: boolean
+  readonly taskPerformance?: TaskPerformance
+  /**
+   * Worker error.
+   */
+  readonly workerError?: WorkerError<ErrorData>
+  /**
+   * Worker id.
+   */
+  readonly workerId?: number
 }
 
 /**
- * Task function properties.
+ * An object holding the task execution response promise resolve/reject callbacks.
+ * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
+ * @internal
  */
-export interface TaskFunctionProperties {
+export interface PromiseResponseWrapper<Response = unknown> {
   /**
-   * Task function name.
+   * The asynchronous resource used to track the task execution.
    */
-  readonly name: string
+  readonly asyncResource?: AsyncResource
   /**
-   * Task function priority. Lower values have higher priority.
+   * Reject callback to reject the promise.
    */
-  readonly priority?: number
+  readonly reject: (reason?: unknown) => void
   /**
-   * Task function worker choice strategy.
+   * Resolve callback to fulfill the promise.
    */
-  readonly strategy?: WorkerChoiceStrategy
+  readonly resolve: (value: PromiseLike<Response> | Response) => void
+  /**
+   * The worker node key executing the task.
+   */
+  readonly workerNodeKey: number
 }
 
 /**
@@ -126,92 +132,86 @@ export interface Task<Data = unknown> {
 }
 
 /**
- * Message object that is passed between main worker and worker.
- * @typeParam Data - Type of data sent to the worker or execution response. This can only be structured-cloneable data.
- * @typeParam ErrorData - Type of data sent to the worker triggering an error. This can only be structured-cloneable data.
- * @internal
+ * Task function properties.
  */
-export interface MessageValue<Data = unknown, ErrorData = unknown>
-  extends Task<Data> {
-  /**
-   * Whether the worker starts or stops its activity check.
-   */
-  readonly checkActive?: boolean
+export interface TaskFunctionProperties {
   /**
-   * Kill code.
+   * Task function name.
    */
-  readonly kill?: 'failure' | 'success' | KillBehavior | true
+  readonly name: string
   /**
-   * Message port.
+   * Task function priority. Lower values have higher priority.
    */
-  readonly port?: MessagePort
+  readonly priority?: number
   /**
-   * Whether the worker is ready or not.
+   * Task function worker choice strategy.
    */
-  readonly ready?: boolean
+  readonly strategy?: WorkerChoiceStrategy
+}
+
+/**
+ * Task performance.
+ * @internal
+ */
+export interface TaskPerformance {
   /**
-   * Whether the worker computes the given statistics or not.
+   * Task event loop utilization.
    */
-  readonly statistics?: WorkerStatistics
+  readonly elu?: EventLoopUtilization
   /**
-   * Task function serialized to string.
+   * Task name.
    */
-  readonly taskFunction?: string
+  readonly name: string
   /**
-   * Task function operation:
-   * - `'add'` - Add a task function.
-   * - `'remove'` - Remove a task function.
-   * - `'default'` - Set a task function as default.
+   * Task runtime.
    */
-  readonly taskFunctionOperation?: 'add' | 'default' | 'remove'
+  readonly runTime?: number
   /**
-   * Whether the task function operation is successful or not.
+   * Task performance timestamp.
    */
-  readonly taskFunctionOperationStatus?: boolean
+  readonly timestamp: number
+}
+
+/**
+ * Worker error.
+ * @typeParam Data - Type of data sent to the worker triggering an error. This can only be structured-cloneable data.
+ */
+export interface WorkerError<Data = unknown> {
   /**
-   * Task function properties.
+   * Data triggering the error.
    */
-  readonly taskFunctionProperties?: TaskFunctionProperties
+  readonly data?: Data
   /**
-   * Task functions properties.
+   * Error object.
    */
-  readonly taskFunctionsProperties?: TaskFunctionProperties[]
+  readonly error?: Error
   /**
-   * Task performance.
+   * Error message.
    */
-  readonly taskPerformance?: TaskPerformance
+  readonly message: string
   /**
-   * Worker error.
+   * Task function name triggering the error.
    */
-  readonly workerError?: WorkerError<ErrorData>
+  readonly name?: string
   /**
-   * Worker id.
+   * Error stack trace.
    */
-  readonly workerId?: number
+  readonly stack?: string
 }
 
 /**
- * An object holding the task execution response promise resolve/reject callbacks.
- * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
+ * Worker task performance statistics computation settings.
  * @internal
  */
-export interface PromiseResponseWrapper<Response = unknown> {
-  /**
-   * The asynchronous resource used to track the task execution.
-   */
-  readonly asyncResource?: AsyncResource
-  /**
-   * Reject callback to reject the promise.
-   */
-  readonly reject: (reason?: unknown) => void
+export interface WorkerStatistics {
   /**
-   * Resolve callback to fulfill the promise.
+   * Whether the worker computes the task event loop utilization (ELU) or not.
    */
-  readonly resolve: (value: PromiseLike<Response> | Response) => void
+  readonly elu: boolean
   /**
-   * The worker node key executing the task.
+   * Whether the worker computes the task runtime or not.
    */
-  readonly workerNodeKey: number
+  readonly runTime: boolean
 }
 
 /**
index 9549d18c32d158b8026ed620c72379134176ce72..25bf144e73bbb39c8c1e5d862b981778d4e75d37 100644 (file)
@@ -74,105 +74,6 @@ export abstract class AbstractWorker<
    */
   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,
-          name,
-          ...this.handleError(
-            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-            new Error(`Task function '${name!}' not found`)
-          ),
-        },
-      })
-      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,
-            name,
-            ...this.handleError(error as Error),
-          },
-        })
-      })
-      .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,
-          name,
-          ...this.handleError(error as Error),
-        },
-      })
-    } finally {
-      this.updateLastTaskTimestamp()
-    }
-  }
-
   /**
    * Performance statistics computation requirements.
    */
@@ -207,6 +108,148 @@ export abstract class AbstractWorker<
     }
   }
 
+  /**
+   * 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 { error: error as Error, status: false }
+    }
+  }
+
+  /**
+   * 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.
+   */
+  public hasTaskFunction (name: string): TaskFunctionOperationResult {
+    try {
+      checkTaskFunctionName(name)
+    } catch (error) {
+      return { error: error as Error, status: false }
+    }
+    return { status: this.taskFunctions.has(name) }
+  }
+
+  /**
+   * 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,
+    ]
+  }
+
+  /**
+   * 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 }
+    }
+  }
+
+  /**
+   * 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 }
+    }
+  }
+
   /**
    * Returns the main worker.
    * @returns Reference to the main worker.
@@ -349,6 +392,105 @@ export abstract class AbstractWorker<
     }
   }
 
+  /**
+   * 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,
+          name,
+          ...this.handleError(
+            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+            new Error(`Task function '${name!}' not found`)
+          ),
+        },
+      })
+      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,
+            name,
+            ...this.handleError(error as Error),
+          },
+        })
+      })
+      .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,
+          name,
+          ...this.handleError(error as Error),
+        },
+      })
+    } finally {
+      this.updateLastTaskTimestamp()
+    }
+  }
+
   /**
    * Sends task functions properties to the main worker.
    */
@@ -505,146 +647,4 @@ export abstract class AbstractWorker<
       this.lastTaskTimestamp = performance.now()
     }
   }
-
-  /**
-   * 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 { error: error as Error, status: false }
-    }
-  }
-
-  /**
-   * 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.
-   */
-  public hasTaskFunction (name: string): TaskFunctionOperationResult {
-    try {
-      checkTaskFunctionName(name)
-    } catch (error) {
-      return { error: error as Error, status: false }
-    }
-    return { status: this.taskFunctions.has(name) }
-  }
-
-  /**
-   * 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,
-    ]
-  }
-
-  /**
-   * 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 }
-    }
-  }
-
-  /**
-   * 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 5a611e80c74e49d670a6a801d38c3ce8c7db87f4..394e367431da4dadd5dc7f420bad041424e9d8c9 100644 (file)
@@ -24,13 +24,8 @@ export class ClusterWorker<
   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>)
+  protected get id (): number {
+    return this.getMainWorker().id
   }
 
   /**
@@ -71,7 +66,12 @@ export class ClusterWorker<
   }
 
   /** @inheritDoc */
-  protected get id (): number {
-    return this.getMainWorker().id
+  protected readonly sendToMainWorker = (
+    message: MessageValue<Response>
+  ): void => {
+    this.getMainWorker().send({
+      ...message,
+      workerId: this.id,
+    } satisfies MessageValue<Response>)
   }
 }
index 9e4067304294337f5819d20293e31a3a719993d9..11099d3c186b42131fa926ef7082e5c7191436e2 100644 (file)
@@ -1,16 +1,5 @@
 import type { WorkerChoiceStrategy } from '../pools/selection-strategies/selection-strategies-types.js'
 
-/**
- * Task synchronous function that can be executed.
- * @param data - Data sent to the worker.
- * @returns Execution response.
- * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
- * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
- */
-export type TaskSyncFunction<Data = unknown, Response = unknown> = (
-  data?: Data
-) => Response
-
 /**
  * Task asynchronous function that can be executed.
  * This function must return a promise.
@@ -53,6 +42,14 @@ export interface TaskFunctionObject<Data = unknown, Response = unknown> {
   taskFunction: TaskFunction<Data, Response>
 }
 
+/**
+ * Task function operation result.
+ */
+export interface TaskFunctionOperationResult {
+  error?: Error
+  status: boolean
+}
+
 /**
  * Tasks functions that can be executed.
  * The key is the name of the task function or task function object.
@@ -66,9 +63,12 @@ export type TaskFunctions<Data = unknown, Response = unknown> = Record<
 >
 
 /**
- * Task function operation result.
+ * Task synchronous function that can be executed.
+ * @param data - Data sent to the worker.
+ * @returns Execution response.
+ * @typeParam Data - Type of data sent to the worker. This can only be structured-cloneable data.
+ * @typeParam Response - Type of execution response. This can only be structured-cloneable data.
  */
-export interface TaskFunctionOperationResult {
-  error?: Error
-  status: boolean
-}
+export type TaskSyncFunction<Data = unknown, Response = unknown> = (
+  data?: Data
+) => Response
index 513acbd5fe55ac8bdab9ce6fb73908bb8725d674..9cbba3a57059d289c50d278bc9d78472b9d3d99b 100644 (file)
@@ -29,13 +29,8 @@ export class ThreadWorker<
   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>)
+  protected get id (): number {
+    return threadId
   }
 
   /**
@@ -97,7 +92,12 @@ export class ThreadWorker<
   }
 
   /** @inheritDoc */
-  protected get id (): number {
-    return threadId
+  protected readonly sendToMainWorker = (
+    message: MessageValue<Response>
+  ): void => {
+    this.port?.postMessage({
+      ...message,
+      workerId: this.id,
+    } satisfies MessageValue<Response>)
   }
 }
index d0f56367d9ab0bb464df4fb713954fe938e85484..753cb31d67452ca02ccb1239a5c2f60ccfb0cb7a 100644 (file)
@@ -4,8 +4,8 @@ import { MessageChannel, Worker as ThreadWorker } from 'node:worker_threads'
 
 import { CircularBuffer } from '../../lib/circular-buffer.cjs'
 import { WorkerTypes } from '../../lib/index.cjs'
-import { MeasurementHistorySize } from '../../lib/pools/worker.cjs'
 import { WorkerNode } from '../../lib/pools/worker-node.cjs'
+import { MeasurementHistorySize } from '../../lib/pools/worker.cjs'
 import { PriorityQueue } from '../../lib/queues/priority-queue.cjs'
 import { DEFAULT_TASK_NAME } from '../../lib/utils.cjs'
 
index 2001ddcd531deddfa40bd9bfc933b38052d34398..53360e85a823d8a1993421cbecd3039fa93e6232 100644 (file)
@@ -181,10 +181,10 @@ describe('Utils test suite', () => {
     expect(isAsyncFunction(async function () {})).toBe(true)
     expect(isAsyncFunction(async function named () {})).toBe(true)
     class TestClass {
-      testArrowAsync = async () => {}
-      testArrowSync = () => {}
       static async testStaticAsync () {}
       static testStaticSync () {}
+      testArrowAsync = async () => {}
+      testArrowSync = () => {}
       async testAsync () {}
       testSync () {}
     }