build(deps-dev): apply updates
[poolifier.git] / tests / pools / abstract-pool.test.mjs
index 041c62a2b1277c179d689dbe182d6b382fc55cef..7054c46a3c02acc4a8fbc0efd1e601fcf6103bf8 100644 (file)
@@ -1,9 +1,15 @@
+// eslint-disable-next-line n/no-unsupported-features/node-builtins
+import { createHook, executionAsyncId } from 'node:async_hooks'
 import { EventEmitterAsyncResource } from 'node:events'
-import { dirname, join } from 'node:path'
 import { readFileSync } from 'node:fs'
+import { dirname, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
+
 import { expect } from 'expect'
 import { restore, stub } from 'sinon'
+
+import { CircularArray } from '../../lib/circular-array.cjs'
+import { Deque } from '../../lib/deque.cjs'
 import {
   DynamicClusterPool,
   DynamicThreadPool,
@@ -13,12 +19,10 @@ import {
   PoolTypes,
   WorkerChoiceStrategies,
   WorkerTypes
-} from '../../lib/index.js'
-import { CircularArray } from '../../lib/circular-array.js'
-import { Deque } from '../../lib/deque.js'
-import { DEFAULT_TASK_NAME } from '../../lib/utils.js'
-import { waitPoolEvents } from '../test-utils.js'
-import { WorkerNode } from '../../lib/pools/worker-node.js'
+} from '../../lib/index.cjs'
+import { WorkerNode } from '../../lib/pools/worker-node.cjs'
+import { DEFAULT_TASK_NAME } from '../../lib/utils.cjs'
+import { waitPoolEvents } from '../test-utils.cjs'
 
 describe('Abstract pool test suite', () => {
   const version = JSON.parse(
@@ -38,7 +42,16 @@ describe('Abstract pool test suite', () => {
     restore()
   })
 
-  it('Simulate pool creation from a non main thread/process', () => {
+  it('Verify that pool can be created and destroyed', async () => {
+    const pool = new FixedThreadPool(
+      numberOfWorkers,
+      './tests/worker-files/thread/testWorker.mjs'
+    )
+    expect(pool).toBeInstanceOf(FixedThreadPool)
+    await pool.destroy()
+  })
+
+  it('Verify that pool cannot be created from a non main thread/process', () => {
     expect(
       () =>
         new StubPoolWithIsMain(
@@ -48,7 +61,7 @@ describe('Abstract pool test suite', () => {
             errorHandler: e => console.error(e)
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new Error(
         'Cannot start a pool from a worker with the same type as the pool'
       )
@@ -60,30 +73,22 @@ describe('Abstract pool test suite', () => {
       numberOfWorkers,
       './tests/worker-files/thread/testWorker.mjs'
     )
-    expect(pool.starting).toBe(false)
     expect(pool.started).toBe(true)
+    expect(pool.starting).toBe(false)
+    expect(pool.destroying).toBe(false)
     await pool.destroy()
   })
 
   it('Verify that filePath is checked', () => {
-    const expectedError = new Error(
-      'Please specify a file with a worker implementation'
-    )
-    expect(() => new FixedThreadPool(numberOfWorkers)).toThrowError(
-      expectedError
+    expect(() => new FixedThreadPool(numberOfWorkers)).toThrow(
+      new TypeError('The worker file path must be specified')
     )
-    expect(() => new FixedThreadPool(numberOfWorkers, '')).toThrowError(
-      expectedError
-    )
-    expect(() => new FixedThreadPool(numberOfWorkers, 0)).toThrowError(
-      expectedError
-    )
-    expect(() => new FixedThreadPool(numberOfWorkers, true)).toThrowError(
-      expectedError
+    expect(() => new FixedThreadPool(numberOfWorkers, 0)).toThrow(
+      new TypeError('The worker file path must be a string')
     )
     expect(
       () => new FixedThreadPool(numberOfWorkers, './dummyWorker.ts')
-    ).toThrowError(new Error("Cannot find the worker file './dummyWorker.ts'"))
+    ).toThrow(new Error("Cannot find the worker file './dummyWorker.ts'"))
   })
 
   it('Verify that numberOfWorkers is checked', () => {
@@ -93,7 +98,7 @@ describe('Abstract pool test suite', () => {
           undefined,
           './tests/worker-files/thread/testWorker.mjs'
         )
-    ).toThrowError(
+    ).toThrow(
       new Error(
         'Cannot instantiate a pool without specifying the number of workers'
       )
@@ -103,8 +108,8 @@ describe('Abstract pool test suite', () => {
   it('Verify that a negative number of workers is checked', () => {
     expect(
       () =>
-        new FixedClusterPool(-1, './tests/worker-files/cluster/testWorker.js')
-    ).toThrowError(
+        new FixedClusterPool(-1, './tests/worker-files/cluster/testWorker.cjs')
+    ).toThrow(
       new RangeError(
         'Cannot instantiate a pool with a negative number of workers'
       )
@@ -115,22 +120,38 @@ describe('Abstract pool test suite', () => {
     expect(
       () =>
         new FixedThreadPool(0.25, './tests/worker-files/thread/testWorker.mjs')
-    ).toThrowError(
+    ).toThrow(
       new TypeError(
         'Cannot instantiate a pool with a non safe integer number of workers'
       )
     )
   })
 
+  it('Verify that pool arguments number and pool type are checked', () => {
+    expect(
+      () =>
+        new FixedThreadPool(
+          numberOfWorkers,
+          './tests/worker-files/thread/testWorker.mjs',
+          undefined,
+          numberOfWorkers * 2
+        )
+    ).toThrow(
+      new Error(
+        'Cannot instantiate a fixed pool with a maximum number of workers specified at initialization'
+      )
+    )
+  })
+
   it('Verify that dynamic pool sizing is checked', () => {
     expect(
       () =>
         new DynamicClusterPool(
           1,
           undefined,
-          './tests/worker-files/cluster/testWorker.js'
+          './tests/worker-files/cluster/testWorker.cjs'
         )
-    ).toThrowError(
+    ).toThrow(
       new TypeError(
         'Cannot instantiate a dynamic pool without specifying the maximum pool size'
       )
@@ -142,7 +163,7 @@ describe('Abstract pool test suite', () => {
           1,
           './tests/worker-files/thread/testWorker.mjs'
         )
-    ).toThrowError(
+    ).toThrow(
       new TypeError(
         'Cannot instantiate a pool with a non safe integer number of workers'
       )
@@ -152,9 +173,9 @@ describe('Abstract pool test suite', () => {
         new DynamicClusterPool(
           0,
           0.5,
-          './tests/worker-files/cluster/testWorker.js'
+          './tests/worker-files/cluster/testWorker.cjs'
         )
-    ).toThrowError(
+    ).toThrow(
       new TypeError(
         'Cannot instantiate a dynamic pool with a non safe integer maximum pool size'
       )
@@ -166,7 +187,7 @@ describe('Abstract pool test suite', () => {
           1,
           './tests/worker-files/thread/testWorker.mjs'
         )
-    ).toThrowError(
+    ).toThrow(
       new RangeError(
         'Cannot instantiate a dynamic pool with a maximum pool size inferior to the minimum pool size'
       )
@@ -178,7 +199,7 @@ describe('Abstract pool test suite', () => {
           0,
           './tests/worker-files/thread/testWorker.mjs'
         )
-    ).toThrowError(
+    ).toThrow(
       new RangeError(
         'Cannot instantiate a dynamic pool with a maximum pool size equal to zero'
       )
@@ -188,9 +209,9 @@ describe('Abstract pool test suite', () => {
         new DynamicClusterPool(
           1,
           1,
-          './tests/worker-files/cluster/testWorker.js'
+          './tests/worker-files/cluster/testWorker.cjs'
         )
-    ).toThrowError(
+    ).toThrow(
       new RangeError(
         'Cannot instantiate a dynamic pool with a minimum pool size equal to the maximum pool size. Use a fixed pool instead'
       )
@@ -203,32 +224,24 @@ describe('Abstract pool test suite', () => {
       './tests/worker-files/thread/testWorker.mjs'
     )
     expect(pool.emitter).toBeInstanceOf(EventEmitterAsyncResource)
+    expect(pool.emitter.eventNames()).toStrictEqual([])
     expect(pool.opts).toStrictEqual({
       startWorkers: true,
       enableEvents: true,
       restartWorkerOnError: true,
       enableTasksQueue: false,
-      workerChoiceStrategy: WorkerChoiceStrategies.ROUND_ROBIN,
-      workerChoiceStrategyOptions: {
-        retries: 6,
-        runTime: { median: false },
-        waitTime: { median: false },
-        elu: { median: false }
-      }
-    })
-    expect(pool.workerChoiceStrategyContext.opts).toStrictEqual({
-      retries: 6,
-      runTime: { median: false },
-      waitTime: { median: false },
-      elu: { median: false }
+      workerChoiceStrategy: WorkerChoiceStrategies.ROUND_ROBIN
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
-        retries: 6,
         runTime: { median: false },
         waitTime: { median: false },
-        elu: { median: false }
+        elu: { median: false },
+        weights: expect.objectContaining({
+          0: expect.any(Number),
+          [pool.info.maxSize - 1]: expect.any(Number)
+        })
       })
     }
     await pool.destroy()
@@ -262,14 +275,12 @@ describe('Abstract pool test suite', () => {
         concurrency: 2,
         size: Math.pow(numberOfWorkers, 2),
         taskStealing: true,
-        tasksStealingOnBackPressure: true
+        tasksStealingOnBackPressure: true,
+        tasksFinishedTimeout: 2000
       },
       workerChoiceStrategy: WorkerChoiceStrategies.LEAST_USED,
       workerChoiceStrategyOptions: {
-        retries: 6,
         runTime: { median: true },
-        waitTime: { median: false },
-        elu: { median: false },
         weights: { 0: 300, 1: 200 }
       },
       onlineHandler: testHandler,
@@ -277,17 +288,9 @@ describe('Abstract pool test suite', () => {
       errorHandler: testHandler,
       exitHandler: testHandler
     })
-    expect(pool.workerChoiceStrategyContext.opts).toStrictEqual({
-      retries: 6,
-      runTime: { median: true },
-      waitTime: { median: false },
-      elu: { median: false },
-      weights: { 0: 300, 1: 200 }
-    })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
-        retries: 6,
         runTime: { median: true },
         waitTime: { median: false },
         elu: { median: false },
@@ -307,41 +310,7 @@ describe('Abstract pool test suite', () => {
             workerChoiceStrategy: 'invalidStrategy'
           }
         )
-    ).toThrowError(
-      new Error("Invalid worker choice strategy 'invalidStrategy'")
-    )
-    expect(
-      () =>
-        new FixedThreadPool(
-          numberOfWorkers,
-          './tests/worker-files/thread/testWorker.mjs',
-          {
-            workerChoiceStrategyOptions: {
-              retries: 'invalidChoiceRetries'
-            }
-          }
-        )
-    ).toThrowError(
-      new TypeError(
-        'Invalid worker choice strategy options: retries must be an integer'
-      )
-    )
-    expect(
-      () =>
-        new FixedThreadPool(
-          numberOfWorkers,
-          './tests/worker-files/thread/testWorker.mjs',
-          {
-            workerChoiceStrategyOptions: {
-              retries: -1
-            }
-          }
-        )
-    ).toThrowError(
-      new RangeError(
-        "Invalid worker choice strategy options: retries '-1' must be greater or equal than zero"
-      )
-    )
+    ).toThrow(new Error("Invalid worker choice strategy 'invalidStrategy'"))
     expect(
       () =>
         new FixedThreadPool(
@@ -351,7 +320,7 @@ describe('Abstract pool test suite', () => {
             workerChoiceStrategyOptions: { weights: {} }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new Error(
         'Invalid worker choice strategy options: must have a weight for each worker node'
       )
@@ -365,7 +334,7 @@ describe('Abstract pool test suite', () => {
             workerChoiceStrategyOptions: { measurement: 'invalidMeasurement' }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new Error(
         "Invalid worker choice strategy options: invalid measurement 'invalidMeasurement'"
       )
@@ -380,7 +349,7 @@ describe('Abstract pool test suite', () => {
             tasksQueueOptions: 'invalidTasksQueueOptions'
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new TypeError('Invalid tasks queue options: must be a plain object')
     )
     expect(
@@ -393,7 +362,7 @@ describe('Abstract pool test suite', () => {
             tasksQueueOptions: { concurrency: 0 }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new RangeError(
         'Invalid worker node tasks concurrency: 0 is a negative integer or zero'
       )
@@ -408,7 +377,7 @@ describe('Abstract pool test suite', () => {
             tasksQueueOptions: { concurrency: -1 }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new RangeError(
         'Invalid worker node tasks concurrency: -1 is a negative integer or zero'
       )
@@ -423,7 +392,7 @@ describe('Abstract pool test suite', () => {
             tasksQueueOptions: { concurrency: 0.2 }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new TypeError('Invalid worker node tasks concurrency: must be an integer')
     )
     expect(
@@ -436,7 +405,7 @@ describe('Abstract pool test suite', () => {
             tasksQueueOptions: { size: 0 }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new RangeError(
         'Invalid worker node tasks queue size: 0 is a negative integer or zero'
       )
@@ -451,7 +420,7 @@ describe('Abstract pool test suite', () => {
             tasksQueueOptions: { size: -1 }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new RangeError(
         'Invalid worker node tasks queue size: -1 is a negative integer or zero'
       )
@@ -466,7 +435,7 @@ describe('Abstract pool test suite', () => {
             tasksQueueOptions: { size: 0.2 }
           }
         )
-    ).toThrowError(
+    ).toThrow(
       new TypeError('Invalid worker node tasks queue size: must be an integer')
     )
   })
@@ -477,25 +446,17 @@ describe('Abstract pool test suite', () => {
       './tests/worker-files/thread/testWorker.mjs',
       { workerChoiceStrategy: WorkerChoiceStrategies.FAIR_SHARE }
     )
-    expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
-      retries: 6,
-      runTime: { median: false },
-      waitTime: { median: false },
-      elu: { median: false }
-    })
-    expect(pool.workerChoiceStrategyContext.opts).toStrictEqual({
-      retries: 6,
-      runTime: { median: false },
-      waitTime: { median: false },
-      elu: { median: false }
-    })
+    expect(pool.opts.workerChoiceStrategyOptions).toBeUndefined()
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
-        retries: 6,
         runTime: { median: false },
         waitTime: { median: false },
-        elu: { median: false }
+        elu: { median: false },
+        weights: expect.objectContaining({
+          0: expect.any(Number),
+          [pool.info.maxSize - 1]: expect.any(Number)
+        })
       })
     }
     expect(
@@ -522,24 +483,19 @@ describe('Abstract pool test suite', () => {
       elu: { median: true }
     })
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
-      retries: 6,
       runTime: { median: true },
-      waitTime: { median: false },
-      elu: { median: true }
-    })
-    expect(pool.workerChoiceStrategyContext.opts).toStrictEqual({
-      retries: 6,
-      runTime: { median: true },
-      waitTime: { median: false },
       elu: { median: true }
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
-        retries: 6,
         runTime: { median: true },
         waitTime: { median: false },
-        elu: { median: true }
+        elu: { median: true },
+        weights: expect.objectContaining({
+          0: expect.any(Number),
+          [pool.info.maxSize - 1]: expect.any(Number)
+        })
       })
     }
     expect(
@@ -566,24 +522,19 @@ describe('Abstract pool test suite', () => {
       elu: { median: false }
     })
     expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({
-      retries: 6,
       runTime: { median: false },
-      waitTime: { median: false },
-      elu: { median: false }
-    })
-    expect(pool.workerChoiceStrategyContext.opts).toStrictEqual({
-      retries: 6,
-      runTime: { median: false },
-      waitTime: { median: false },
       elu: { median: false }
     })
     for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext
       .workerChoiceStrategies) {
       expect(workerChoiceStrategy.opts).toStrictEqual({
-        retries: 6,
         runTime: { median: false },
         waitTime: { median: false },
-        elu: { median: false }
+        elu: { median: false },
+        weights: expect.objectContaining({
+          0: expect.any(Number),
+          [pool.info.maxSize - 1]: expect.any(Number)
+        })
       })
     }
     expect(
@@ -607,37 +558,19 @@ describe('Abstract pool test suite', () => {
     })
     expect(() =>
       pool.setWorkerChoiceStrategyOptions('invalidWorkerChoiceStrategyOptions')
-    ).toThrowError(
+    ).toThrow(
       new TypeError(
         'Invalid worker choice strategy options: must be a plain object'
       )
     )
-    expect(() =>
-      pool.setWorkerChoiceStrategyOptions({
-        retries: 'invalidChoiceRetries'
-      })
-    ).toThrowError(
-      new TypeError(
-        'Invalid worker choice strategy options: retries must be an integer'
-      )
-    )
-    expect(() =>
-      pool.setWorkerChoiceStrategyOptions({ retries: -1 })
-    ).toThrowError(
-      new RangeError(
-        "Invalid worker choice strategy options: retries '-1' must be greater or equal than zero"
-      )
-    )
-    expect(() =>
-      pool.setWorkerChoiceStrategyOptions({ weights: {} })
-    ).toThrowError(
+    expect(() => pool.setWorkerChoiceStrategyOptions({ weights: {} })).toThrow(
       new Error(
         'Invalid worker choice strategy options: must have a weight for each worker node'
       )
     )
     expect(() =>
       pool.setWorkerChoiceStrategyOptions({ measurement: 'invalidMeasurement' })
-    ).toThrowError(
+    ).toThrow(
       new Error(
         "Invalid worker choice strategy options: invalid measurement 'invalidMeasurement'"
       )
@@ -652,41 +585,27 @@ describe('Abstract pool test suite', () => {
     )
     expect(pool.opts.enableTasksQueue).toBe(false)
     expect(pool.opts.tasksQueueOptions).toBeUndefined()
-    for (const workerNode of pool.workerNodes) {
-      expect(workerNode.onEmptyQueue).toBeUndefined()
-      expect(workerNode.onBackPressure).toBeUndefined()
-    }
     pool.enableTasksQueue(true)
     expect(pool.opts.enableTasksQueue).toBe(true)
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 1,
       size: Math.pow(numberOfWorkers, 2),
       taskStealing: true,
-      tasksStealingOnBackPressure: true
+      tasksStealingOnBackPressure: true,
+      tasksFinishedTimeout: 2000
     })
-    for (const workerNode of pool.workerNodes) {
-      expect(workerNode.onEmptyQueue).toBeInstanceOf(Function)
-      expect(workerNode.onBackPressure).toBeInstanceOf(Function)
-    }
     pool.enableTasksQueue(true, { concurrency: 2 })
     expect(pool.opts.enableTasksQueue).toBe(true)
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 2,
       size: Math.pow(numberOfWorkers, 2),
       taskStealing: true,
-      tasksStealingOnBackPressure: true
+      tasksStealingOnBackPressure: true,
+      tasksFinishedTimeout: 2000
     })
-    for (const workerNode of pool.workerNodes) {
-      expect(workerNode.onEmptyQueue).toBeInstanceOf(Function)
-      expect(workerNode.onBackPressure).toBeInstanceOf(Function)
-    }
     pool.enableTasksQueue(false)
     expect(pool.opts.enableTasksQueue).toBe(false)
     expect(pool.opts.tasksQueueOptions).toBeUndefined()
-    for (const workerNode of pool.workerNodes) {
-      expect(workerNode.onEmptyQueue).toBeUndefined()
-      expect(workerNode.onBackPressure).toBeUndefined()
-    }
     await pool.destroy()
   })
 
@@ -700,33 +619,32 @@ describe('Abstract pool test suite', () => {
       concurrency: 1,
       size: Math.pow(numberOfWorkers, 2),
       taskStealing: true,
-      tasksStealingOnBackPressure: true
+      tasksStealingOnBackPressure: true,
+      tasksFinishedTimeout: 2000
     })
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.tasksQueueBackPressureSize).toBe(
         pool.opts.tasksQueueOptions.size
       )
-      expect(workerNode.onEmptyQueue).toBeInstanceOf(Function)
-      expect(workerNode.onBackPressure).toBeInstanceOf(Function)
     }
     pool.setTasksQueueOptions({
       concurrency: 2,
       size: 2,
       taskStealing: false,
-      tasksStealingOnBackPressure: false
+      tasksStealingOnBackPressure: false,
+      tasksFinishedTimeout: 3000
     })
     expect(pool.opts.tasksQueueOptions).toStrictEqual({
       concurrency: 2,
       size: 2,
       taskStealing: false,
-      tasksStealingOnBackPressure: false
+      tasksStealingOnBackPressure: false,
+      tasksFinishedTimeout: 3000
     })
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.tasksQueueBackPressureSize).toBe(
         pool.opts.tasksQueueOptions.size
       )
-      expect(workerNode.onEmptyQueue).toBeUndefined()
-      expect(workerNode.onBackPressure).toBeUndefined()
     }
     pool.setTasksQueueOptions({
       concurrency: 1,
@@ -737,44 +655,41 @@ describe('Abstract pool test suite', () => {
       concurrency: 1,
       size: Math.pow(numberOfWorkers, 2),
       taskStealing: true,
-      tasksStealingOnBackPressure: true
+      tasksStealingOnBackPressure: true,
+      tasksFinishedTimeout: 2000
     })
     for (const workerNode of pool.workerNodes) {
       expect(workerNode.tasksQueueBackPressureSize).toBe(
         pool.opts.tasksQueueOptions.size
       )
-      expect(workerNode.onEmptyQueue).toBeInstanceOf(Function)
-      expect(workerNode.onBackPressure).toBeInstanceOf(Function)
     }
-    expect(() =>
-      pool.setTasksQueueOptions('invalidTasksQueueOptions')
-    ).toThrowError(
+    expect(() => pool.setTasksQueueOptions('invalidTasksQueueOptions')).toThrow(
       new TypeError('Invalid tasks queue options: must be a plain object')
     )
-    expect(() => pool.setTasksQueueOptions({ concurrency: 0 })).toThrowError(
+    expect(() => pool.setTasksQueueOptions({ concurrency: 0 })).toThrow(
       new RangeError(
         'Invalid worker node tasks concurrency: 0 is a negative integer or zero'
       )
     )
-    expect(() => pool.setTasksQueueOptions({ concurrency: -1 })).toThrowError(
+    expect(() => pool.setTasksQueueOptions({ concurrency: -1 })).toThrow(
       new RangeError(
         'Invalid worker node tasks concurrency: -1 is a negative integer or zero'
       )
     )
-    expect(() => pool.setTasksQueueOptions({ concurrency: 0.2 })).toThrowError(
+    expect(() => pool.setTasksQueueOptions({ concurrency: 0.2 })).toThrow(
       new TypeError('Invalid worker node tasks concurrency: must be an integer')
     )
-    expect(() => pool.setTasksQueueOptions({ size: 0 })).toThrowError(
+    expect(() => pool.setTasksQueueOptions({ size: 0 })).toThrow(
       new RangeError(
         'Invalid worker node tasks queue size: 0 is a negative integer or zero'
       )
     )
-    expect(() => pool.setTasksQueueOptions({ size: -1 })).toThrowError(
+    expect(() => pool.setTasksQueueOptions({ size: -1 })).toThrow(
       new RangeError(
         'Invalid worker node tasks queue size: -1 is a negative integer or zero'
       )
     )
-    expect(() => pool.setTasksQueueOptions({ size: 0.2 })).toThrowError(
+    expect(() => pool.setTasksQueueOptions({ size: 0.2 })).toThrow(
       new TypeError('Invalid worker node tasks queue size: must be an integer')
     )
     await pool.destroy()
@@ -792,6 +707,7 @@ describe('Abstract pool test suite', () => {
       started: true,
       ready: true,
       strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      strategyRetries: 0,
       minSize: numberOfWorkers,
       maxSize: numberOfWorkers,
       workerNodes: numberOfWorkers,
@@ -805,7 +721,7 @@ describe('Abstract pool test suite', () => {
     pool = new DynamicClusterPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     expect(pool.info).toStrictEqual({
       version,
@@ -814,6 +730,7 @@ describe('Abstract pool test suite', () => {
       started: true,
       ready: true,
       strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      strategyRetries: 0,
       minSize: Math.floor(numberOfWorkers / 2),
       maxSize: numberOfWorkers,
       workerNodes: Math.floor(numberOfWorkers / 2),
@@ -829,7 +746,7 @@ describe('Abstract pool test suite', () => {
   it('Verify that pool worker tasks usage are initialized', async () => {
     const pool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     for (const workerNode of pool.workerNodes) {
       expect(workerNode).toBeInstanceOf(WorkerNode)
@@ -839,6 +756,7 @@ describe('Abstract pool test suite', () => {
           executing: 0,
           queued: 0,
           maxQueued: 0,
+          sequentiallyStolen: 0,
           stolen: 0,
           failed: 0
         },
@@ -864,7 +782,7 @@ describe('Abstract pool test suite', () => {
   it('Verify that pool worker tasks queue are initialized', async () => {
     let pool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     for (const workerNode of pool.workerNodes) {
       expect(workerNode).toBeInstanceOf(WorkerNode)
@@ -890,7 +808,7 @@ describe('Abstract pool test suite', () => {
   it('Verify that pool worker info are initialized', async () => {
     let pool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     for (const workerNode of pool.workerNodes) {
       expect(workerNode).toBeInstanceOf(WorkerNode)
@@ -898,7 +816,8 @@ describe('Abstract pool test suite', () => {
         id: expect.any(Number),
         type: WorkerTypes.cluster,
         dynamic: false,
-        ready: true
+        ready: true,
+        stealing: false
       })
     }
     await pool.destroy()
@@ -913,16 +832,35 @@ describe('Abstract pool test suite', () => {
         id: expect.any(Number),
         type: WorkerTypes.thread,
         dynamic: false,
-        ready: true
+        ready: true,
+        stealing: false
       })
     }
     await pool.destroy()
   })
 
+  it('Verify that pool statuses are checked at start or destroy', async () => {
+    const pool = new FixedThreadPool(
+      numberOfWorkers,
+      './tests/worker-files/thread/testWorker.mjs'
+    )
+    expect(pool.info.started).toBe(true)
+    expect(pool.info.ready).toBe(true)
+    expect(() => pool.start()).toThrow(
+      new Error('Cannot start an already started pool')
+    )
+    await pool.destroy()
+    expect(pool.info.started).toBe(false)
+    expect(pool.info.ready).toBe(false)
+    await expect(pool.destroy()).rejects.toThrow(
+      new Error('Cannot destroy an already destroyed pool')
+    )
+  })
+
   it('Verify that pool can be started after initialization', async () => {
     const pool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js',
+      './tests/worker-files/cluster/testWorker.cjs',
       {
         startWorkers: false
       }
@@ -930,12 +868,15 @@ describe('Abstract pool test suite', () => {
     expect(pool.info.started).toBe(false)
     expect(pool.info.ready).toBe(false)
     expect(pool.workerNodes).toStrictEqual([])
-    await expect(pool.execute()).rejects.toThrowError(
+    expect(pool.readyEventEmitted).toBe(false)
+    await expect(pool.execute()).rejects.toThrow(
       new Error('Cannot execute a task on not started pool')
     )
     pool.start()
     expect(pool.info.started).toBe(true)
     expect(pool.info.ready).toBe(true)
+    await waitPoolEvents(pool, PoolEvents.ready, 1)
+    expect(pool.readyEventEmitted).toBe(true)
     expect(pool.workerNodes.length).toBe(numberOfWorkers)
     for (const workerNode of pool.workerNodes) {
       expect(workerNode).toBeInstanceOf(WorkerNode)
@@ -946,22 +887,22 @@ describe('Abstract pool test suite', () => {
   it('Verify that pool execute() arguments are checked', async () => {
     const pool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
-    await expect(pool.execute(undefined, 0)).rejects.toThrowError(
+    await expect(pool.execute(undefined, 0)).rejects.toThrow(
       new TypeError('name argument must be a string')
     )
-    await expect(pool.execute(undefined, '')).rejects.toThrowError(
+    await expect(pool.execute(undefined, '')).rejects.toThrow(
       new TypeError('name argument must not be an empty string')
     )
-    await expect(pool.execute(undefined, undefined, {})).rejects.toThrowError(
+    await expect(pool.execute(undefined, undefined, {})).rejects.toThrow(
       new TypeError('transferList argument must be an array')
     )
     await expect(pool.execute(undefined, 'unknown')).rejects.toBe(
       "Task function 'unknown' not found"
     )
     await pool.destroy()
-    await expect(pool.execute()).rejects.toThrowError(
+    await expect(pool.execute()).rejects.toThrow(
       new Error('Cannot execute a task on not started pool')
     )
   })
@@ -969,7 +910,7 @@ describe('Abstract pool test suite', () => {
   it('Verify that pool worker tasks usage are computed', async () => {
     const pool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     const promises = new Set()
     const maxMultiplier = 2
@@ -983,6 +924,7 @@ describe('Abstract pool test suite', () => {
           executing: maxMultiplier,
           queued: 0,
           maxQueued: 0,
+          sequentiallyStolen: 0,
           stolen: 0,
           failed: 0
         },
@@ -1010,6 +952,7 @@ describe('Abstract pool test suite', () => {
           executing: 0,
           queued: 0,
           maxQueued: 0,
+          sequentiallyStolen: 0,
           stolen: 0,
           failed: 0
         },
@@ -1051,6 +994,7 @@ describe('Abstract pool test suite', () => {
           executing: 0,
           queued: 0,
           maxQueued: 0,
+          sequentiallyStolen: 0,
           stolen: 0,
           failed: 0
         },
@@ -1086,6 +1030,7 @@ describe('Abstract pool test suite', () => {
           executing: 0,
           queued: 0,
           maxQueued: 0,
+          sequentiallyStolen: 0,
           stolen: 0,
           failed: 0
         },
@@ -1116,7 +1061,7 @@ describe('Abstract pool test suite', () => {
     const pool = new DynamicClusterPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     expect(pool.emitter.eventNames()).toStrictEqual([])
     let poolInfo
@@ -1135,6 +1080,7 @@ describe('Abstract pool test suite', () => {
       started: true,
       ready: true,
       strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
       workerNodes: expect.any(Number),
@@ -1175,6 +1121,7 @@ describe('Abstract pool test suite', () => {
       started: true,
       ready: true,
       strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
       workerNodes: expect.any(Number),
@@ -1214,6 +1161,7 @@ describe('Abstract pool test suite', () => {
       started: true,
       ready: true,
       strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
       workerNodes: expect.any(Number),
@@ -1256,10 +1204,12 @@ describe('Abstract pool test suite', () => {
       started: true,
       ready: true,
       strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+      strategyRetries: expect.any(Number),
       minSize: expect.any(Number),
       maxSize: expect.any(Number),
       workerNodes: expect.any(Number),
       idleWorkerNodes: expect.any(Number),
+      stealingWorkerNodes: expect.any(Number),
       busyWorkerNodes: expect.any(Number),
       executedTasks: expect.any(Number),
       executingTasks: expect.any(Number),
@@ -1269,7 +1219,101 @@ describe('Abstract pool test suite', () => {
       stolenTasks: expect.any(Number),
       failedTasks: expect.any(Number)
     })
-    expect(pool.hasBackPressure.called).toBe(true)
+    expect(pool.hasBackPressure.callCount).toBeGreaterThanOrEqual(7)
+    await pool.destroy()
+  })
+
+  it('Verify that destroy() waits for queued tasks to finish', async () => {
+    const tasksFinishedTimeout = 2500
+    const pool = new FixedThreadPool(
+      numberOfWorkers,
+      './tests/worker-files/thread/asyncWorker.mjs',
+      {
+        enableTasksQueue: true,
+        tasksQueueOptions: { tasksFinishedTimeout }
+      }
+    )
+    const maxMultiplier = 4
+    let tasksFinished = 0
+    for (const workerNode of pool.workerNodes) {
+      workerNode.on('taskFinished', () => {
+        ++tasksFinished
+      })
+    }
+    for (let i = 0; i < numberOfWorkers * maxMultiplier; i++) {
+      pool.execute()
+    }
+    expect(pool.info.queuedTasks).toBeGreaterThan(0)
+    const startTime = performance.now()
+    await pool.destroy()
+    const elapsedTime = performance.now() - startTime
+    expect(tasksFinished).toBeLessThanOrEqual(numberOfWorkers * maxMultiplier)
+    expect(elapsedTime).toBeGreaterThanOrEqual(2000)
+    expect(elapsedTime).toBeLessThanOrEqual(tasksFinishedTimeout + 800)
+  })
+
+  it('Verify that destroy() waits until the tasks finished timeout is reached', async () => {
+    const tasksFinishedTimeout = 1000
+    const pool = new FixedThreadPool(
+      numberOfWorkers,
+      './tests/worker-files/thread/asyncWorker.mjs',
+      {
+        enableTasksQueue: true,
+        tasksQueueOptions: { tasksFinishedTimeout }
+      }
+    )
+    const maxMultiplier = 4
+    let tasksFinished = 0
+    for (const workerNode of pool.workerNodes) {
+      workerNode.on('taskFinished', () => {
+        ++tasksFinished
+      })
+    }
+    for (let i = 0; i < numberOfWorkers * maxMultiplier; i++) {
+      pool.execute()
+    }
+    expect(pool.info.queuedTasks).toBeGreaterThan(0)
+    const startTime = performance.now()
+    await pool.destroy()
+    const elapsedTime = performance.now() - startTime
+    expect(tasksFinished).toBe(0)
+    expect(elapsedTime).toBeLessThanOrEqual(tasksFinishedTimeout + 800)
+  })
+
+  it('Verify that pool asynchronous resource track tasks execution', async () => {
+    let taskAsyncId
+    let initCalls = 0
+    let beforeCalls = 0
+    let afterCalls = 0
+    let resolveCalls = 0
+    const hook = createHook({
+      init (asyncId, type) {
+        if (type === 'poolifier:task') {
+          initCalls++
+          taskAsyncId = asyncId
+        }
+      },
+      before (asyncId) {
+        if (asyncId === taskAsyncId) beforeCalls++
+      },
+      after (asyncId) {
+        if (asyncId === taskAsyncId) afterCalls++
+      },
+      promiseResolve () {
+        if (executionAsyncId() === taskAsyncId) resolveCalls++
+      }
+    })
+    const pool = new FixedThreadPool(
+      numberOfWorkers,
+      './tests/worker-files/thread/testWorker.mjs'
+    )
+    hook.enable()
+    await pool.execute()
+    hook.disable()
+    expect(initCalls).toBe(1)
+    expect(beforeCalls).toBe(1)
+    expect(afterCalls).toBe(1)
+    expect(resolveCalls).toBe(1)
     await pool.destroy()
   })
 
@@ -1290,7 +1334,7 @@ describe('Abstract pool test suite', () => {
     await dynamicThreadPool.destroy()
     const fixedClusterPool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.js'
+      './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs'
     )
     await waitPoolEvents(fixedClusterPool, PoolEvents.ready, 1)
     expect(fixedClusterPool.hasTaskFunction(DEFAULT_TASK_NAME)).toBe(true)
@@ -1312,18 +1356,18 @@ describe('Abstract pool test suite', () => {
     await waitPoolEvents(dynamicThreadPool, PoolEvents.ready, 1)
     await expect(
       dynamicThreadPool.addTaskFunction(0, () => {})
-    ).rejects.toThrowError(new TypeError('name argument must be a string'))
+    ).rejects.toThrow(new TypeError('name argument must be a string'))
     await expect(
       dynamicThreadPool.addTaskFunction('', () => {})
-    ).rejects.toThrowError(
+    ).rejects.toThrow(
       new TypeError('name argument must not be an empty string')
     )
-    await expect(
-      dynamicThreadPool.addTaskFunction('test', 0)
-    ).rejects.toThrowError(new TypeError('fn argument must be a function'))
-    await expect(
-      dynamicThreadPool.addTaskFunction('test', '')
-    ).rejects.toThrowError(new TypeError('fn argument must be a function'))
+    await expect(dynamicThreadPool.addTaskFunction('test', 0)).rejects.toThrow(
+      new TypeError('fn argument must be a function')
+    )
+    await expect(dynamicThreadPool.addTaskFunction('test', '')).rejects.toThrow(
+      new TypeError('fn argument must be a function')
+    )
     expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
       DEFAULT_TASK_NAME,
       'test'
@@ -1352,6 +1396,7 @@ describe('Abstract pool test suite', () => {
           executed: expect.any(Number),
           executing: 0,
           queued: 0,
+          sequentiallyStolen: 0,
           stolen: 0,
           failed: 0
         },
@@ -1385,9 +1430,7 @@ describe('Abstract pool test suite', () => {
       DEFAULT_TASK_NAME,
       'test'
     ])
-    await expect(
-      dynamicThreadPool.removeTaskFunction('test')
-    ).rejects.toThrowError(
+    await expect(dynamicThreadPool.removeTaskFunction('test')).rejects.toThrow(
       new Error('Cannot remove a task function not handled on the pool side')
     )
     const echoTaskFunction = data => {
@@ -1431,7 +1474,7 @@ describe('Abstract pool test suite', () => {
     await dynamicThreadPool.destroy()
     const fixedClusterPool = new FixedClusterPool(
       numberOfWorkers,
-      './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.js'
+      './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs'
     )
     await waitPoolEvents(fixedClusterPool, PoolEvents.ready, 1)
     expect(fixedClusterPool.listTaskFunctionNames()).toStrictEqual([
@@ -1450,25 +1493,24 @@ describe('Abstract pool test suite', () => {
       './tests/worker-files/thread/testMultipleTaskFunctionsWorker.mjs'
     )
     await waitPoolEvents(dynamicThreadPool, PoolEvents.ready, 1)
-    await expect(
-      dynamicThreadPool.setDefaultTaskFunction(0)
-    ).rejects.toThrowError(
+    const workerId = dynamicThreadPool.workerNodes[0].info.id
+    await expect(dynamicThreadPool.setDefaultTaskFunction(0)).rejects.toThrow(
       new Error(
-        "Task function operation 'default' failed on worker 31 with error: 'TypeError: name parameter is not a string'"
+        `Task function operation 'default' failed on worker ${workerId} with error: 'TypeError: name parameter is not a string'`
       )
     )
     await expect(
       dynamicThreadPool.setDefaultTaskFunction(DEFAULT_TASK_NAME)
-    ).rejects.toThrowError(
+    ).rejects.toThrow(
       new Error(
-        "Task function operation 'default' failed on worker 31 with error: 'Error: Cannot set the default task function reserved name as the default task function'"
+        `Task function operation 'default' failed on worker ${workerId} with error: 'Error: Cannot set the default task function reserved name as the default task function'`
       )
     )
     await expect(
       dynamicThreadPool.setDefaultTaskFunction('unknown')
-    ).rejects.toThrowError(
+    ).rejects.toThrow(
       new Error(
-        "Task function operation 'default' failed on worker 31 with error: 'Error: Cannot set the default task function to a non-existing task function'"
+        `Task function operation 'default' failed on worker ${workerId} with error: 'Error: Cannot set the default task function to a non-existing task function'`
       )
     )
     expect(dynamicThreadPool.listTaskFunctionNames()).toStrictEqual([
@@ -1495,13 +1537,14 @@ describe('Abstract pool test suite', () => {
       'jsonIntegerSerialization',
       'factorial'
     ])
+    await dynamicThreadPool.destroy()
   })
 
   it('Verify that multiple task functions worker is working', async () => {
     const pool = new DynamicClusterPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
-      './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.js'
+      './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.cjs'
     )
     const data = { n: 10 }
     const result0 = await pool.execute(data)
@@ -1529,6 +1572,7 @@ describe('Abstract pool test suite', () => {
             executing: 0,
             failed: 0,
             queued: 0,
+            sequentiallyStolen: 0,
             stolen: 0
           },
           runTime: {
@@ -1565,7 +1609,7 @@ describe('Abstract pool test suite', () => {
     const pool = new DynamicClusterPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     const workerNodeKey = 0
     await expect(
@@ -1578,7 +1622,7 @@ describe('Abstract pool test suite', () => {
     const pool = new DynamicClusterPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     const workerNodeKey = 0
     await expect(
@@ -1598,7 +1642,7 @@ describe('Abstract pool test suite', () => {
     const pool = new DynamicClusterPool(
       Math.floor(numberOfWorkers / 2),
       numberOfWorkers,
-      './tests/worker-files/cluster/testWorker.js'
+      './tests/worker-files/cluster/testWorker.cjs'
     )
     await expect(
       pool.sendTaskFunctionOperationToWorkers({