+ expect(pool.opts.enableTasksQueue).toBe(false)
+ expect(pool.opts.tasksQueueOptions).toBeUndefined()
+ pool.enableTasksQueue(true)
+ expect(pool.opts.enableTasksQueue).toBe(true)
+ expect(pool.opts.tasksQueueOptions).toStrictEqual({
+ concurrency: 1,
+ size: 4
+ })
+ pool.enableTasksQueue(true, { concurrency: 2 })
+ expect(pool.opts.enableTasksQueue).toBe(true)
+ expect(pool.opts.tasksQueueOptions).toStrictEqual({
+ concurrency: 2,
+ size: 4
+ })
+ pool.enableTasksQueue(false)
+ expect(pool.opts.enableTasksQueue).toBe(false)
+ expect(pool.opts.tasksQueueOptions).toBeUndefined()
+ await pool.destroy()
+ })
+
+ it('Verify that pool tasks queue options can be set', async () => {
+ const pool = new FixedThreadPool(
+ numberOfWorkers,
+ './tests/worker-files/thread/testWorker.js',
+ { enableTasksQueue: true }
+ )
+ expect(pool.opts.tasksQueueOptions).toStrictEqual({
+ concurrency: 1,
+ size: 4
+ })
+ pool.setTasksQueueOptions({ concurrency: 2 })
+ expect(pool.opts.tasksQueueOptions).toStrictEqual({
+ concurrency: 2,
+ size: 4
+ })
+ expect(() =>
+ pool.setTasksQueueOptions('invalidTasksQueueOptions')
+ ).toThrowError(
+ new TypeError('Invalid tasks queue options: must be a plain object')
+ )
+ expect(() => pool.setTasksQueueOptions({ concurrency: 0 })).toThrowError(
+ new RangeError(
+ 'Invalid worker node tasks concurrency: 0 is a negative integer or zero'
+ )
+ )
+ expect(() => pool.setTasksQueueOptions({ concurrency: -1 })).toThrowError(
+ new RangeError(
+ 'Invalid worker node tasks concurrency: -1 is a negative integer or zero'
+ )
+ )
+ expect(() => pool.setTasksQueueOptions({ concurrency: 0.2 })).toThrowError(
+ new TypeError('Invalid worker node tasks concurrency: must be an integer')
+ )
+ expect(() => pool.setTasksQueueOptions({ queueMaxSize: 2 })).toThrowError(
+ new Error(
+ 'Invalid tasks queue options: queueMaxSize is deprecated, please use size instead'
+ )
+ )
+ expect(() => pool.setTasksQueueOptions({ size: 0 })).toThrowError(
+ new RangeError(
+ 'Invalid worker node tasks queue size: 0 is a negative integer or zero'
+ )
+ )
+ expect(() => pool.setTasksQueueOptions({ size: -1 })).toThrowError(
+ new RangeError(
+ 'Invalid worker node tasks queue size: -1 is a negative integer or zero'
+ )
+ )
+ expect(() => pool.setTasksQueueOptions({ size: 0.2 })).toThrowError(
+ new TypeError('Invalid worker node tasks queue size: must be an integer')
+ )
+ await pool.destroy()
+ })
+
+ it('Verify that pool info is set', async () => {
+ let pool = new FixedThreadPool(
+ numberOfWorkers,
+ './tests/worker-files/thread/testWorker.js'
+ )
+ expect(pool.info).toStrictEqual({
+ version,
+ type: PoolTypes.fixed,
+ worker: WorkerTypes.thread,
+ ready: true,
+ strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+ minSize: numberOfWorkers,
+ maxSize: numberOfWorkers,
+ workerNodes: numberOfWorkers,
+ idleWorkerNodes: numberOfWorkers,
+ busyWorkerNodes: 0,
+ executedTasks: 0,
+ executingTasks: 0,
+ failedTasks: 0
+ })
+ await pool.destroy()
+ pool = new DynamicClusterPool(
+ Math.floor(numberOfWorkers / 2),
+ numberOfWorkers,
+ './tests/worker-files/cluster/testWorker.js'
+ )
+ expect(pool.info).toStrictEqual({
+ version,
+ type: PoolTypes.dynamic,
+ worker: WorkerTypes.cluster,
+ ready: true,
+ strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+ minSize: Math.floor(numberOfWorkers / 2),
+ maxSize: numberOfWorkers,
+ workerNodes: Math.floor(numberOfWorkers / 2),
+ idleWorkerNodes: Math.floor(numberOfWorkers / 2),
+ busyWorkerNodes: 0,
+ executedTasks: 0,
+ executingTasks: 0,
+ failedTasks: 0
+ })
+ await pool.destroy()
+ })
+
+ it('Verify that pool worker tasks usage are initialized', async () => {
+ const pool = new FixedClusterPool(
+ numberOfWorkers,
+ './tests/worker-files/cluster/testWorker.js'
+ )
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.usage).toStrictEqual({
+ tasks: {
+ executed: 0,
+ executing: 0,
+ queued: 0,
+ maxQueued: 0,
+ stolen: 0,
+ failed: 0
+ },
+ runTime: {
+ history: new CircularArray()
+ },
+ waitTime: {
+ history: new CircularArray()
+ },
+ elu: {
+ idle: {
+ history: new CircularArray()
+ },
+ active: {
+ history: new CircularArray()
+ }
+ }
+ })
+ }
+ await pool.destroy()
+ })
+
+ it('Verify that pool worker tasks queue are initialized', async () => {
+ let pool = new FixedClusterPool(
+ numberOfWorkers,
+ './tests/worker-files/cluster/testWorker.js'
+ )
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.tasksQueue).toBeDefined()
+ expect(workerNode.tasksQueue).toBeInstanceOf(Deque)
+ expect(workerNode.tasksQueue.size).toBe(0)
+ expect(workerNode.tasksQueue.maxSize).toBe(0)
+ }
+ await pool.destroy()
+ pool = new DynamicThreadPool(
+ Math.floor(numberOfWorkers / 2),
+ numberOfWorkers,
+ './tests/worker-files/thread/testWorker.js'
+ )
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.tasksQueue).toBeDefined()
+ expect(workerNode.tasksQueue).toBeInstanceOf(Deque)
+ expect(workerNode.tasksQueue.size).toBe(0)
+ expect(workerNode.tasksQueue.maxSize).toBe(0)
+ }
+ await pool.destroy()
+ })
+
+ it('Verify that pool worker info are initialized', async () => {
+ let pool = new FixedClusterPool(
+ numberOfWorkers,
+ './tests/worker-files/cluster/testWorker.js'
+ )
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.info).toStrictEqual({
+ id: expect.any(Number),
+ type: WorkerTypes.cluster,
+ dynamic: false,
+ ready: true
+ })
+ }
+ await pool.destroy()
+ pool = new DynamicThreadPool(
+ Math.floor(numberOfWorkers / 2),
+ numberOfWorkers,
+ './tests/worker-files/thread/testWorker.js'
+ )
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.info).toStrictEqual({
+ id: expect.any(Number),
+ type: WorkerTypes.thread,
+ dynamic: false,
+ ready: true
+ })
+ }
+ await pool.destroy()
+ })
+
+ it('Verify that pool execute() arguments are checked', async () => {
+ const pool = new FixedClusterPool(
+ numberOfWorkers,
+ './tests/worker-files/cluster/testWorker.js'
+ )
+ await expect(pool.execute(undefined, 0)).rejects.toThrowError(
+ new TypeError('name argument must be a string')
+ )
+ await expect(pool.execute(undefined, '')).rejects.toThrowError(
+ new TypeError('name argument must not be an empty string')
+ )
+ await expect(pool.execute(undefined, undefined, {})).rejects.toThrowError(
+ 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(undefined, undefined, {})).rejects.toThrowError(
+ new Error('Cannot execute a task on destroyed pool')
+ )
+ })
+
+ it('Verify that pool worker tasks usage are computed', async () => {
+ const pool = new FixedClusterPool(
+ numberOfWorkers,
+ './tests/worker-files/cluster/testWorker.js'
+ )
+ const promises = new Set()
+ const maxMultiplier = 2
+ for (let i = 0; i < numberOfWorkers * maxMultiplier; i++) {
+ promises.add(pool.execute())
+ }
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.usage).toStrictEqual({
+ tasks: {
+ executed: 0,
+ executing: maxMultiplier,
+ queued: 0,
+ maxQueued: 0,
+ stolen: 0,
+ failed: 0
+ },
+ runTime: {
+ history: expect.any(CircularArray)
+ },
+ waitTime: {
+ history: expect.any(CircularArray)
+ },
+ elu: {
+ idle: {
+ history: expect.any(CircularArray)
+ },
+ active: {
+ history: expect.any(CircularArray)
+ }
+ }
+ })
+ }
+ await Promise.all(promises)
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.usage).toStrictEqual({
+ tasks: {
+ executed: maxMultiplier,
+ executing: 0,
+ queued: 0,
+ maxQueued: 0,
+ stolen: 0,
+ failed: 0
+ },
+ runTime: {
+ history: expect.any(CircularArray)
+ },
+ waitTime: {
+ history: expect.any(CircularArray)
+ },
+ elu: {
+ idle: {
+ history: expect.any(CircularArray)
+ },
+ active: {
+ history: expect.any(CircularArray)
+ }
+ }
+ })
+ }
+ await pool.destroy()
+ })
+
+ it('Verify that pool worker tasks usage are reset at worker choice strategy change', async () => {
+ const pool = new DynamicThreadPool(
+ Math.floor(numberOfWorkers / 2),
+ numberOfWorkers,
+ './tests/worker-files/thread/testWorker.js'
+ )
+ const promises = new Set()
+ const maxMultiplier = 2
+ for (let i = 0; i < numberOfWorkers * maxMultiplier; i++) {
+ promises.add(pool.execute())
+ }
+ await Promise.all(promises)
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.usage).toStrictEqual({
+ tasks: {
+ executed: expect.any(Number),
+ executing: 0,
+ queued: 0,
+ maxQueued: 0,
+ stolen: 0,
+ failed: 0
+ },
+ runTime: {
+ history: expect.any(CircularArray)
+ },
+ waitTime: {
+ history: expect.any(CircularArray)
+ },
+ elu: {
+ idle: {
+ history: expect.any(CircularArray)
+ },
+ active: {
+ history: expect.any(CircularArray)
+ }
+ }
+ })
+ expect(workerNode.usage.tasks.executed).toBeGreaterThan(0)
+ expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(
+ numberOfWorkers * maxMultiplier
+ )
+ expect(workerNode.usage.runTime.history.length).toBe(0)
+ expect(workerNode.usage.waitTime.history.length).toBe(0)
+ expect(workerNode.usage.elu.idle.history.length).toBe(0)
+ expect(workerNode.usage.elu.active.history.length).toBe(0)
+ }
+ pool.setWorkerChoiceStrategy(WorkerChoiceStrategies.FAIR_SHARE)
+ for (const workerNode of pool.workerNodes) {
+ expect(workerNode.usage).toStrictEqual({
+ tasks: {
+ executed: 0,
+ executing: 0,
+ queued: 0,
+ maxQueued: 0,
+ stolen: 0,
+ failed: 0
+ },
+ runTime: {
+ history: expect.any(CircularArray)
+ },
+ waitTime: {
+ history: expect.any(CircularArray)
+ },
+ elu: {
+ idle: {
+ history: expect.any(CircularArray)
+ },
+ active: {
+ history: expect.any(CircularArray)
+ }
+ }
+ })
+ expect(workerNode.usage.runTime.history.length).toBe(0)
+ expect(workerNode.usage.waitTime.history.length).toBe(0)
+ expect(workerNode.usage.elu.idle.history.length).toBe(0)
+ expect(workerNode.usage.elu.active.history.length).toBe(0)
+ }
+ await pool.destroy()
+ })
+
+ it("Verify that pool event emitter 'ready' event can register a callback", async () => {
+ const pool = new DynamicClusterPool(
+ Math.floor(numberOfWorkers / 2),
+ numberOfWorkers,
+ './tests/worker-files/cluster/testWorker.js'
+ )
+ let poolInfo
+ let poolReady = 0
+ pool.emitter.on(PoolEvents.ready, (info) => {
+ ++poolReady
+ poolInfo = info
+ })
+ await waitPoolEvents(pool, PoolEvents.ready, 1)
+ expect(poolReady).toBe(1)
+ expect(poolInfo).toStrictEqual({
+ version,
+ type: PoolTypes.dynamic,
+ worker: WorkerTypes.cluster,
+ ready: true,
+ strategy: WorkerChoiceStrategies.ROUND_ROBIN,
+ minSize: expect.any(Number),
+ maxSize: expect.any(Number),
+ workerNodes: expect.any(Number),
+ idleWorkerNodes: expect.any(Number),
+ busyWorkerNodes: expect.any(Number),
+ executedTasks: expect.any(Number),
+ executingTasks: expect.any(Number),
+ failedTasks: expect.any(Number)
+ })
+ await pool.destroy()
+ })
+
+ it("Verify that pool event emitter 'busy' event can register a callback", async () => {
+ const pool = new FixedThreadPool(
+ numberOfWorkers,
+ './tests/worker-files/thread/testWorker.js'
+ )
+ const promises = new Set()