Commit | Line | Data |
---|---|---|
85aeb3f3 | 1 | const { MessageChannel } = require('worker_threads') |
a61a0724 | 2 | const { expect } = require('expect') |
e843b904 | 3 | const { |
70a4f5ea | 4 | DynamicClusterPool, |
9e619829 | 5 | DynamicThreadPool, |
aee46736 | 6 | FixedClusterPool, |
e843b904 | 7 | FixedThreadPool, |
aee46736 | 8 | PoolEvents, |
184855e6 | 9 | PoolTypes, |
3d6dd312 | 10 | WorkerChoiceStrategies, |
184855e6 | 11 | WorkerTypes |
cdace0e5 | 12 | } = require('../../../lib') |
78099a15 | 13 | const { CircularArray } = require('../../../lib/circular-array') |
29ee7e9a | 14 | const { Queue } = require('../../../lib/queue') |
23ccf9d7 | 15 | const { version } = require('../../../package.json') |
2431bdb4 | 16 | const { waitPoolEvents } = require('../../test-utils') |
e1ffb94f JB |
17 | |
18 | describe('Abstract pool test suite', () => { | |
fc027381 | 19 | const numberOfWorkers = 2 |
a8884ffd | 20 | class StubPoolWithIsMain extends FixedThreadPool { |
e1ffb94f JB |
21 | isMain () { |
22 | return false | |
23 | } | |
3ec964d6 | 24 | } |
3ec964d6 | 25 | |
3ec964d6 | 26 | it('Simulate pool creation from a non main thread/process', () => { |
8d3782fa JB |
27 | expect( |
28 | () => | |
a8884ffd | 29 | new StubPoolWithIsMain( |
7c0ba920 | 30 | numberOfWorkers, |
8d3782fa JB |
31 | './tests/worker-files/thread/testWorker.js', |
32 | { | |
8ebe6c30 | 33 | errorHandler: (e) => console.error(e) |
8d3782fa JB |
34 | } |
35 | ) | |
04f45163 | 36 | ).toThrowError( |
8c6d4acf | 37 | 'Cannot start a pool from a worker with the same type as the pool' |
04f45163 | 38 | ) |
3ec964d6 | 39 | }) |
c510fea7 APA |
40 | |
41 | it('Verify that filePath is checked', () => { | |
292ad316 JB |
42 | const expectedError = new Error( |
43 | 'Please specify a file with a worker implementation' | |
44 | ) | |
7c0ba920 | 45 | expect(() => new FixedThreadPool(numberOfWorkers)).toThrowError( |
292ad316 | 46 | expectedError |
8d3782fa | 47 | ) |
7c0ba920 | 48 | expect(() => new FixedThreadPool(numberOfWorkers, '')).toThrowError( |
292ad316 | 49 | expectedError |
8d3782fa | 50 | ) |
3d6dd312 JB |
51 | expect(() => new FixedThreadPool(numberOfWorkers, 0)).toThrowError( |
52 | expectedError | |
53 | ) | |
54 | expect(() => new FixedThreadPool(numberOfWorkers, true)).toThrowError( | |
55 | expectedError | |
56 | ) | |
57 | expect( | |
58 | () => new FixedThreadPool(numberOfWorkers, './dummyWorker.ts') | |
59 | ).toThrowError(new Error("Cannot find the worker file './dummyWorker.ts'")) | |
8d3782fa JB |
60 | }) |
61 | ||
62 | it('Verify that numberOfWorkers is checked', () => { | |
63 | expect(() => new FixedThreadPool()).toThrowError( | |
d4aeae5a | 64 | 'Cannot instantiate a pool without specifying the number of workers' |
8d3782fa JB |
65 | ) |
66 | }) | |
67 | ||
68 | it('Verify that a negative number of workers is checked', () => { | |
69 | expect( | |
70 | () => | |
71 | new FixedClusterPool(-1, './tests/worker-files/cluster/testWorker.js') | |
72 | ).toThrowError( | |
473c717a JB |
73 | new RangeError( |
74 | 'Cannot instantiate a pool with a negative number of workers' | |
75 | ) | |
8d3782fa JB |
76 | ) |
77 | }) | |
78 | ||
79 | it('Verify that a non integer number of workers is checked', () => { | |
80 | expect( | |
81 | () => | |
82 | new FixedThreadPool(0.25, './tests/worker-files/thread/testWorker.js') | |
83 | ).toThrowError( | |
473c717a | 84 | new TypeError( |
0d80593b | 85 | 'Cannot instantiate a pool with a non safe integer number of workers' |
8d3782fa JB |
86 | ) |
87 | ) | |
c510fea7 | 88 | }) |
7c0ba920 | 89 | |
216541b6 | 90 | it('Verify that dynamic pool sizing is checked', () => { |
a5ed75b7 JB |
91 | expect( |
92 | () => | |
93 | new DynamicClusterPool( | |
94 | 1, | |
95 | undefined, | |
96 | './tests/worker-files/cluster/testWorker.js' | |
97 | ) | |
98 | ).toThrowError( | |
99 | new TypeError( | |
100 | 'Cannot instantiate a dynamic pool without specifying the maximum pool size' | |
101 | ) | |
102 | ) | |
2761efb4 JB |
103 | expect( |
104 | () => | |
105 | new DynamicThreadPool( | |
106 | 0.5, | |
107 | 1, | |
108 | './tests/worker-files/thread/testWorker.js' | |
109 | ) | |
110 | ).toThrowError( | |
111 | new TypeError( | |
112 | 'Cannot instantiate a pool with a non safe integer number of workers' | |
113 | ) | |
114 | ) | |
115 | expect( | |
116 | () => | |
117 | new DynamicClusterPool( | |
118 | 0, | |
119 | 0.5, | |
a5ed75b7 | 120 | './tests/worker-files/cluster/testWorker.js' |
2761efb4 JB |
121 | ) |
122 | ).toThrowError( | |
123 | new TypeError( | |
124 | 'Cannot instantiate a dynamic pool with a non safe integer maximum pool size' | |
125 | ) | |
126 | ) | |
2431bdb4 JB |
127 | expect( |
128 | () => | |
129 | new DynamicThreadPool(2, 1, './tests/worker-files/thread/testWorker.js') | |
130 | ).toThrowError( | |
131 | new RangeError( | |
132 | 'Cannot instantiate a dynamic pool with a maximum pool size inferior to the minimum pool size' | |
133 | ) | |
134 | ) | |
135 | expect( | |
136 | () => | |
2761efb4 JB |
137 | new DynamicClusterPool( |
138 | 1, | |
139 | 1, | |
a5ed75b7 | 140 | './tests/worker-files/cluster/testWorker.js' |
2761efb4 | 141 | ) |
2431bdb4 JB |
142 | ).toThrowError( |
143 | new RangeError( | |
144 | 'Cannot instantiate a dynamic pool with a minimum pool size equal to the maximum pool size. Use a fixed pool instead' | |
145 | ) | |
146 | ) | |
21f710aa JB |
147 | expect( |
148 | () => | |
149 | new DynamicThreadPool(0, 0, './tests/worker-files/thread/testWorker.js') | |
150 | ).toThrowError( | |
151 | new RangeError( | |
d640b48b | 152 | 'Cannot instantiate a dynamic pool with a maximum pool size equal to zero' |
21f710aa JB |
153 | ) |
154 | ) | |
2431bdb4 JB |
155 | }) |
156 | ||
fd7ebd49 | 157 | it('Verify that pool options are checked', async () => { |
7c0ba920 JB |
158 | let pool = new FixedThreadPool( |
159 | numberOfWorkers, | |
160 | './tests/worker-files/thread/testWorker.js' | |
161 | ) | |
7c0ba920 | 162 | expect(pool.emitter).toBeDefined() |
1f68cede JB |
163 | expect(pool.opts.enableEvents).toBe(true) |
164 | expect(pool.opts.restartWorkerOnError).toBe(true) | |
ff733df7 | 165 | expect(pool.opts.enableTasksQueue).toBe(false) |
d4aeae5a | 166 | expect(pool.opts.tasksQueueOptions).toBeUndefined() |
e843b904 JB |
167 | expect(pool.opts.workerChoiceStrategy).toBe( |
168 | WorkerChoiceStrategies.ROUND_ROBIN | |
169 | ) | |
da309861 | 170 | expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ |
932fc8be | 171 | runTime: { median: false }, |
5df69fab JB |
172 | waitTime: { median: false }, |
173 | elu: { median: false } | |
da309861 | 174 | }) |
35cf1c03 JB |
175 | expect(pool.opts.messageHandler).toBeUndefined() |
176 | expect(pool.opts.errorHandler).toBeUndefined() | |
177 | expect(pool.opts.onlineHandler).toBeUndefined() | |
178 | expect(pool.opts.exitHandler).toBeUndefined() | |
fd7ebd49 | 179 | await pool.destroy() |
73bfd59d | 180 | const testHandler = () => console.info('test handler executed') |
7c0ba920 JB |
181 | pool = new FixedThreadPool( |
182 | numberOfWorkers, | |
183 | './tests/worker-files/thread/testWorker.js', | |
184 | { | |
e4543b14 | 185 | workerChoiceStrategy: WorkerChoiceStrategies.LEAST_USED, |
49be33fe | 186 | workerChoiceStrategyOptions: { |
932fc8be | 187 | runTime: { median: true }, |
fc027381 | 188 | weights: { 0: 300, 1: 200 } |
49be33fe | 189 | }, |
35cf1c03 | 190 | enableEvents: false, |
1f68cede | 191 | restartWorkerOnError: false, |
ff733df7 | 192 | enableTasksQueue: true, |
d4aeae5a | 193 | tasksQueueOptions: { concurrency: 2 }, |
35cf1c03 JB |
194 | messageHandler: testHandler, |
195 | errorHandler: testHandler, | |
196 | onlineHandler: testHandler, | |
197 | exitHandler: testHandler | |
7c0ba920 JB |
198 | } |
199 | ) | |
7c0ba920 | 200 | expect(pool.emitter).toBeUndefined() |
1f68cede JB |
201 | expect(pool.opts.enableEvents).toBe(false) |
202 | expect(pool.opts.restartWorkerOnError).toBe(false) | |
ff733df7 | 203 | expect(pool.opts.enableTasksQueue).toBe(true) |
d4aeae5a | 204 | expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 2 }) |
e843b904 | 205 | expect(pool.opts.workerChoiceStrategy).toBe( |
e4543b14 | 206 | WorkerChoiceStrategies.LEAST_USED |
e843b904 | 207 | ) |
da309861 | 208 | expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ |
932fc8be | 209 | runTime: { median: true }, |
fc027381 | 210 | weights: { 0: 300, 1: 200 } |
da309861 | 211 | }) |
35cf1c03 JB |
212 | expect(pool.opts.messageHandler).toStrictEqual(testHandler) |
213 | expect(pool.opts.errorHandler).toStrictEqual(testHandler) | |
214 | expect(pool.opts.onlineHandler).toStrictEqual(testHandler) | |
215 | expect(pool.opts.exitHandler).toStrictEqual(testHandler) | |
fd7ebd49 | 216 | await pool.destroy() |
7c0ba920 JB |
217 | }) |
218 | ||
a20f0ba5 | 219 | it('Verify that pool options are validated', async () => { |
d4aeae5a JB |
220 | expect( |
221 | () => | |
222 | new FixedThreadPool( | |
223 | numberOfWorkers, | |
224 | './tests/worker-files/thread/testWorker.js', | |
225 | { | |
f0d7f803 | 226 | workerChoiceStrategy: 'invalidStrategy' |
d4aeae5a JB |
227 | } |
228 | ) | |
f0d7f803 | 229 | ).toThrowError("Invalid worker choice strategy 'invalidStrategy'") |
d4aeae5a JB |
230 | expect( |
231 | () => | |
232 | new FixedThreadPool( | |
233 | numberOfWorkers, | |
234 | './tests/worker-files/thread/testWorker.js', | |
235 | { | |
f0d7f803 | 236 | workerChoiceStrategyOptions: 'invalidOptions' |
d4aeae5a JB |
237 | } |
238 | ) | |
f0d7f803 JB |
239 | ).toThrowError( |
240 | 'Invalid worker choice strategy options: must be a plain object' | |
241 | ) | |
49be33fe JB |
242 | expect( |
243 | () => | |
244 | new FixedThreadPool( | |
245 | numberOfWorkers, | |
246 | './tests/worker-files/thread/testWorker.js', | |
247 | { | |
248 | workerChoiceStrategyOptions: { weights: {} } | |
249 | } | |
250 | ) | |
251 | ).toThrowError( | |
252 | 'Invalid worker choice strategy options: must have a weight for each worker node' | |
253 | ) | |
f0d7f803 JB |
254 | expect( |
255 | () => | |
256 | new FixedThreadPool( | |
257 | numberOfWorkers, | |
258 | './tests/worker-files/thread/testWorker.js', | |
259 | { | |
260 | workerChoiceStrategyOptions: { measurement: 'invalidMeasurement' } | |
261 | } | |
262 | ) | |
263 | ).toThrowError( | |
264 | "Invalid worker choice strategy options: invalid measurement 'invalidMeasurement'" | |
265 | ) | |
266 | expect( | |
267 | () => | |
268 | new FixedThreadPool( | |
269 | numberOfWorkers, | |
270 | './tests/worker-files/thread/testWorker.js', | |
271 | { | |
272 | enableTasksQueue: true, | |
273 | tasksQueueOptions: { concurrency: 0 } | |
274 | } | |
275 | ) | |
276 | ).toThrowError("Invalid worker tasks concurrency '0'") | |
277 | expect( | |
278 | () => | |
279 | new FixedThreadPool( | |
280 | numberOfWorkers, | |
281 | './tests/worker-files/thread/testWorker.js', | |
282 | { | |
283 | enableTasksQueue: true, | |
284 | tasksQueueOptions: 'invalidTasksQueueOptions' | |
285 | } | |
286 | ) | |
287 | ).toThrowError('Invalid tasks queue options: must be a plain object') | |
288 | expect( | |
289 | () => | |
290 | new FixedThreadPool( | |
291 | numberOfWorkers, | |
292 | './tests/worker-files/thread/testWorker.js', | |
293 | { | |
294 | enableTasksQueue: true, | |
295 | tasksQueueOptions: { concurrency: 0.2 } | |
296 | } | |
297 | ) | |
298 | ).toThrowError('Invalid worker tasks concurrency: must be an integer') | |
d4aeae5a JB |
299 | }) |
300 | ||
2431bdb4 | 301 | it('Verify that pool worker choice strategy options can be set', async () => { |
a20f0ba5 JB |
302 | const pool = new FixedThreadPool( |
303 | numberOfWorkers, | |
304 | './tests/worker-files/thread/testWorker.js', | |
305 | { workerChoiceStrategy: WorkerChoiceStrategies.FAIR_SHARE } | |
306 | ) | |
307 | expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ | |
932fc8be | 308 | runTime: { median: false }, |
5df69fab JB |
309 | waitTime: { median: false }, |
310 | elu: { median: false } | |
a20f0ba5 JB |
311 | }) |
312 | for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext | |
313 | .workerChoiceStrategies) { | |
86bf340d | 314 | expect(workerChoiceStrategy.opts).toStrictEqual({ |
932fc8be | 315 | runTime: { median: false }, |
5df69fab JB |
316 | waitTime: { median: false }, |
317 | elu: { median: false } | |
86bf340d | 318 | }) |
a20f0ba5 | 319 | } |
87de9ff5 JB |
320 | expect( |
321 | pool.workerChoiceStrategyContext.getTaskStatisticsRequirements() | |
322 | ).toStrictEqual({ | |
932fc8be JB |
323 | runTime: { |
324 | aggregate: true, | |
325 | average: true, | |
326 | median: false | |
327 | }, | |
328 | waitTime: { | |
329 | aggregate: false, | |
330 | average: false, | |
331 | median: false | |
332 | }, | |
5df69fab | 333 | elu: { |
9adcefab JB |
334 | aggregate: true, |
335 | average: true, | |
5df69fab JB |
336 | median: false |
337 | } | |
86bf340d | 338 | }) |
9adcefab JB |
339 | pool.setWorkerChoiceStrategyOptions({ |
340 | runTime: { median: true }, | |
341 | elu: { median: true } | |
342 | }) | |
a20f0ba5 | 343 | expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ |
9adcefab JB |
344 | runTime: { median: true }, |
345 | elu: { median: true } | |
a20f0ba5 JB |
346 | }) |
347 | for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext | |
348 | .workerChoiceStrategies) { | |
932fc8be | 349 | expect(workerChoiceStrategy.opts).toStrictEqual({ |
9adcefab JB |
350 | runTime: { median: true }, |
351 | elu: { median: true } | |
932fc8be | 352 | }) |
a20f0ba5 | 353 | } |
87de9ff5 JB |
354 | expect( |
355 | pool.workerChoiceStrategyContext.getTaskStatisticsRequirements() | |
356 | ).toStrictEqual({ | |
932fc8be JB |
357 | runTime: { |
358 | aggregate: true, | |
359 | average: false, | |
360 | median: true | |
361 | }, | |
362 | waitTime: { | |
363 | aggregate: false, | |
364 | average: false, | |
365 | median: false | |
366 | }, | |
5df69fab | 367 | elu: { |
9adcefab | 368 | aggregate: true, |
5df69fab | 369 | average: false, |
9adcefab | 370 | median: true |
5df69fab | 371 | } |
86bf340d | 372 | }) |
9adcefab JB |
373 | pool.setWorkerChoiceStrategyOptions({ |
374 | runTime: { median: false }, | |
375 | elu: { median: false } | |
376 | }) | |
a20f0ba5 | 377 | expect(pool.opts.workerChoiceStrategyOptions).toStrictEqual({ |
9adcefab JB |
378 | runTime: { median: false }, |
379 | elu: { median: false } | |
a20f0ba5 JB |
380 | }) |
381 | for (const [, workerChoiceStrategy] of pool.workerChoiceStrategyContext | |
382 | .workerChoiceStrategies) { | |
932fc8be | 383 | expect(workerChoiceStrategy.opts).toStrictEqual({ |
9adcefab JB |
384 | runTime: { median: false }, |
385 | elu: { median: false } | |
932fc8be | 386 | }) |
a20f0ba5 | 387 | } |
87de9ff5 JB |
388 | expect( |
389 | pool.workerChoiceStrategyContext.getTaskStatisticsRequirements() | |
390 | ).toStrictEqual({ | |
932fc8be JB |
391 | runTime: { |
392 | aggregate: true, | |
393 | average: true, | |
394 | median: false | |
395 | }, | |
396 | waitTime: { | |
397 | aggregate: false, | |
398 | average: false, | |
399 | median: false | |
400 | }, | |
5df69fab | 401 | elu: { |
9adcefab JB |
402 | aggregate: true, |
403 | average: true, | |
5df69fab JB |
404 | median: false |
405 | } | |
86bf340d | 406 | }) |
1f95d544 JB |
407 | expect(() => |
408 | pool.setWorkerChoiceStrategyOptions('invalidWorkerChoiceStrategyOptions') | |
409 | ).toThrowError( | |
410 | 'Invalid worker choice strategy options: must be a plain object' | |
411 | ) | |
412 | expect(() => | |
413 | pool.setWorkerChoiceStrategyOptions({ weights: {} }) | |
414 | ).toThrowError( | |
415 | 'Invalid worker choice strategy options: must have a weight for each worker node' | |
416 | ) | |
417 | expect(() => | |
418 | pool.setWorkerChoiceStrategyOptions({ measurement: 'invalidMeasurement' }) | |
419 | ).toThrowError( | |
420 | "Invalid worker choice strategy options: invalid measurement 'invalidMeasurement'" | |
421 | ) | |
a20f0ba5 JB |
422 | await pool.destroy() |
423 | }) | |
424 | ||
2431bdb4 | 425 | it('Verify that pool tasks queue can be enabled/disabled', async () => { |
a20f0ba5 JB |
426 | const pool = new FixedThreadPool( |
427 | numberOfWorkers, | |
428 | './tests/worker-files/thread/testWorker.js' | |
429 | ) | |
430 | expect(pool.opts.enableTasksQueue).toBe(false) | |
431 | expect(pool.opts.tasksQueueOptions).toBeUndefined() | |
432 | pool.enableTasksQueue(true) | |
433 | expect(pool.opts.enableTasksQueue).toBe(true) | |
434 | expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 1 }) | |
435 | pool.enableTasksQueue(true, { concurrency: 2 }) | |
436 | expect(pool.opts.enableTasksQueue).toBe(true) | |
437 | expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 2 }) | |
438 | pool.enableTasksQueue(false) | |
439 | expect(pool.opts.enableTasksQueue).toBe(false) | |
440 | expect(pool.opts.tasksQueueOptions).toBeUndefined() | |
441 | await pool.destroy() | |
442 | }) | |
443 | ||
2431bdb4 | 444 | it('Verify that pool tasks queue options can be set', async () => { |
a20f0ba5 JB |
445 | const pool = new FixedThreadPool( |
446 | numberOfWorkers, | |
447 | './tests/worker-files/thread/testWorker.js', | |
448 | { enableTasksQueue: true } | |
449 | ) | |
450 | expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 1 }) | |
451 | pool.setTasksQueueOptions({ concurrency: 2 }) | |
452 | expect(pool.opts.tasksQueueOptions).toStrictEqual({ concurrency: 2 }) | |
f0d7f803 JB |
453 | expect(() => |
454 | pool.setTasksQueueOptions('invalidTasksQueueOptions') | |
455 | ).toThrowError('Invalid tasks queue options: must be a plain object') | |
a20f0ba5 JB |
456 | expect(() => pool.setTasksQueueOptions({ concurrency: 0 })).toThrowError( |
457 | "Invalid worker tasks concurrency '0'" | |
458 | ) | |
f0d7f803 JB |
459 | expect(() => pool.setTasksQueueOptions({ concurrency: 0.2 })).toThrowError( |
460 | 'Invalid worker tasks concurrency: must be an integer' | |
461 | ) | |
a20f0ba5 JB |
462 | await pool.destroy() |
463 | }) | |
464 | ||
6b27d407 JB |
465 | it('Verify that pool info is set', async () => { |
466 | let pool = new FixedThreadPool( | |
467 | numberOfWorkers, | |
468 | './tests/worker-files/thread/testWorker.js' | |
469 | ) | |
2dca6cab JB |
470 | expect(pool.info).toStrictEqual({ |
471 | version, | |
472 | type: PoolTypes.fixed, | |
473 | worker: WorkerTypes.thread, | |
474 | ready: true, | |
475 | strategy: WorkerChoiceStrategies.ROUND_ROBIN, | |
476 | minSize: numberOfWorkers, | |
477 | maxSize: numberOfWorkers, | |
478 | workerNodes: numberOfWorkers, | |
479 | idleWorkerNodes: numberOfWorkers, | |
480 | busyWorkerNodes: 0, | |
481 | executedTasks: 0, | |
482 | executingTasks: 0, | |
2dca6cab JB |
483 | failedTasks: 0 |
484 | }) | |
6b27d407 JB |
485 | await pool.destroy() |
486 | pool = new DynamicClusterPool( | |
2431bdb4 | 487 | Math.floor(numberOfWorkers / 2), |
6b27d407 | 488 | numberOfWorkers, |
ecdfbdc0 | 489 | './tests/worker-files/cluster/testWorker.js' |
6b27d407 | 490 | ) |
2dca6cab JB |
491 | expect(pool.info).toStrictEqual({ |
492 | version, | |
493 | type: PoolTypes.dynamic, | |
494 | worker: WorkerTypes.cluster, | |
495 | ready: true, | |
496 | strategy: WorkerChoiceStrategies.ROUND_ROBIN, | |
497 | minSize: Math.floor(numberOfWorkers / 2), | |
498 | maxSize: numberOfWorkers, | |
499 | workerNodes: Math.floor(numberOfWorkers / 2), | |
500 | idleWorkerNodes: Math.floor(numberOfWorkers / 2), | |
501 | busyWorkerNodes: 0, | |
502 | executedTasks: 0, | |
503 | executingTasks: 0, | |
2dca6cab JB |
504 | failedTasks: 0 |
505 | }) | |
6b27d407 JB |
506 | await pool.destroy() |
507 | }) | |
508 | ||
2431bdb4 | 509 | it('Verify that pool worker tasks usage are initialized', async () => { |
bf9549ae JB |
510 | const pool = new FixedClusterPool( |
511 | numberOfWorkers, | |
512 | './tests/worker-files/cluster/testWorker.js' | |
513 | ) | |
f06e48d8 | 514 | for (const workerNode of pool.workerNodes) { |
465b2940 | 515 | expect(workerNode.usage).toStrictEqual({ |
a4e07f72 JB |
516 | tasks: { |
517 | executed: 0, | |
518 | executing: 0, | |
519 | queued: 0, | |
df593701 | 520 | maxQueued: 0, |
a4e07f72 JB |
521 | failed: 0 |
522 | }, | |
523 | runTime: { | |
a4e07f72 JB |
524 | history: expect.any(CircularArray) |
525 | }, | |
526 | waitTime: { | |
a4e07f72 JB |
527 | history: expect.any(CircularArray) |
528 | }, | |
5df69fab JB |
529 | elu: { |
530 | idle: { | |
5df69fab JB |
531 | history: expect.any(CircularArray) |
532 | }, | |
533 | active: { | |
5df69fab | 534 | history: expect.any(CircularArray) |
f7510105 | 535 | } |
5df69fab | 536 | } |
86bf340d | 537 | }) |
f06e48d8 JB |
538 | } |
539 | await pool.destroy() | |
540 | }) | |
541 | ||
2431bdb4 JB |
542 | it('Verify that pool worker tasks queue are initialized', async () => { |
543 | let pool = new FixedClusterPool( | |
f06e48d8 JB |
544 | numberOfWorkers, |
545 | './tests/worker-files/cluster/testWorker.js' | |
546 | ) | |
547 | for (const workerNode of pool.workerNodes) { | |
548 | expect(workerNode.tasksQueue).toBeDefined() | |
29ee7e9a | 549 | expect(workerNode.tasksQueue).toBeInstanceOf(Queue) |
4d8bf9e4 | 550 | expect(workerNode.tasksQueue.size).toBe(0) |
9c16fb4b | 551 | expect(workerNode.tasksQueue.maxSize).toBe(0) |
bf9549ae | 552 | } |
fd7ebd49 | 553 | await pool.destroy() |
2431bdb4 JB |
554 | pool = new DynamicThreadPool( |
555 | Math.floor(numberOfWorkers / 2), | |
556 | numberOfWorkers, | |
557 | './tests/worker-files/thread/testWorker.js' | |
558 | ) | |
559 | for (const workerNode of pool.workerNodes) { | |
560 | expect(workerNode.tasksQueue).toBeDefined() | |
561 | expect(workerNode.tasksQueue).toBeInstanceOf(Queue) | |
562 | expect(workerNode.tasksQueue.size).toBe(0) | |
563 | expect(workerNode.tasksQueue.maxSize).toBe(0) | |
564 | } | |
565 | }) | |
566 | ||
567 | it('Verify that pool worker info are initialized', async () => { | |
568 | let pool = new FixedClusterPool( | |
569 | numberOfWorkers, | |
570 | './tests/worker-files/cluster/testWorker.js' | |
571 | ) | |
2dca6cab JB |
572 | for (const workerNode of pool.workerNodes) { |
573 | expect(workerNode.info).toStrictEqual({ | |
574 | id: expect.any(Number), | |
575 | type: WorkerTypes.cluster, | |
576 | dynamic: false, | |
577 | ready: true | |
578 | }) | |
579 | } | |
2431bdb4 JB |
580 | await pool.destroy() |
581 | pool = new DynamicThreadPool( | |
582 | Math.floor(numberOfWorkers / 2), | |
583 | numberOfWorkers, | |
584 | './tests/worker-files/thread/testWorker.js' | |
585 | ) | |
2dca6cab JB |
586 | for (const workerNode of pool.workerNodes) { |
587 | expect(workerNode.info).toStrictEqual({ | |
588 | id: expect.any(Number), | |
589 | type: WorkerTypes.thread, | |
590 | dynamic: false, | |
85aeb3f3 JB |
591 | ready: true, |
592 | messageChannel: expect.any(MessageChannel) | |
2dca6cab JB |
593 | }) |
594 | } | |
bf9549ae JB |
595 | }) |
596 | ||
2431bdb4 | 597 | it('Verify that pool worker tasks usage are computed', async () => { |
bf9549ae JB |
598 | const pool = new FixedClusterPool( |
599 | numberOfWorkers, | |
600 | './tests/worker-files/cluster/testWorker.js' | |
601 | ) | |
09c2d0d3 | 602 | const promises = new Set() |
fc027381 JB |
603 | const maxMultiplier = 2 |
604 | for (let i = 0; i < numberOfWorkers * maxMultiplier; i++) { | |
09c2d0d3 | 605 | promises.add(pool.execute()) |
bf9549ae | 606 | } |
f06e48d8 | 607 | for (const workerNode of pool.workerNodes) { |
465b2940 | 608 | expect(workerNode.usage).toStrictEqual({ |
a4e07f72 JB |
609 | tasks: { |
610 | executed: 0, | |
611 | executing: maxMultiplier, | |
612 | queued: 0, | |
df593701 | 613 | maxQueued: 0, |
a4e07f72 JB |
614 | failed: 0 |
615 | }, | |
616 | runTime: { | |
a4e07f72 JB |
617 | history: expect.any(CircularArray) |
618 | }, | |
619 | waitTime: { | |
a4e07f72 JB |
620 | history: expect.any(CircularArray) |
621 | }, | |
5df69fab JB |
622 | elu: { |
623 | idle: { | |
5df69fab JB |
624 | history: expect.any(CircularArray) |
625 | }, | |
626 | active: { | |
5df69fab | 627 | history: expect.any(CircularArray) |
f7510105 | 628 | } |
5df69fab | 629 | } |
86bf340d | 630 | }) |
bf9549ae JB |
631 | } |
632 | await Promise.all(promises) | |
f06e48d8 | 633 | for (const workerNode of pool.workerNodes) { |
465b2940 | 634 | expect(workerNode.usage).toStrictEqual({ |
a4e07f72 JB |
635 | tasks: { |
636 | executed: maxMultiplier, | |
637 | executing: 0, | |
638 | queued: 0, | |
df593701 | 639 | maxQueued: 0, |
a4e07f72 JB |
640 | failed: 0 |
641 | }, | |
642 | runTime: { | |
a4e07f72 JB |
643 | history: expect.any(CircularArray) |
644 | }, | |
645 | waitTime: { | |
a4e07f72 JB |
646 | history: expect.any(CircularArray) |
647 | }, | |
5df69fab JB |
648 | elu: { |
649 | idle: { | |
5df69fab JB |
650 | history: expect.any(CircularArray) |
651 | }, | |
652 | active: { | |
5df69fab | 653 | history: expect.any(CircularArray) |
f7510105 | 654 | } |
5df69fab | 655 | } |
86bf340d | 656 | }) |
bf9549ae | 657 | } |
fd7ebd49 | 658 | await pool.destroy() |
bf9549ae JB |
659 | }) |
660 | ||
2431bdb4 | 661 | it('Verify that pool worker tasks usage are reset at worker choice strategy change', async () => { |
7fd82a1c | 662 | const pool = new DynamicThreadPool( |
2431bdb4 | 663 | Math.floor(numberOfWorkers / 2), |
8f4878b7 | 664 | numberOfWorkers, |
9e619829 JB |
665 | './tests/worker-files/thread/testWorker.js' |
666 | ) | |
09c2d0d3 | 667 | const promises = new Set() |
ee9f5295 JB |
668 | const maxMultiplier = 2 |
669 | for (let i = 0; i < numberOfWorkers * maxMultiplier; i++) { | |
09c2d0d3 | 670 | promises.add(pool.execute()) |
9e619829 JB |
671 | } |
672 | await Promise.all(promises) | |
f06e48d8 | 673 | for (const workerNode of pool.workerNodes) { |
465b2940 | 674 | expect(workerNode.usage).toStrictEqual({ |
a4e07f72 JB |
675 | tasks: { |
676 | executed: expect.any(Number), | |
677 | executing: 0, | |
678 | queued: 0, | |
df593701 | 679 | maxQueued: 0, |
a4e07f72 JB |
680 | failed: 0 |
681 | }, | |
682 | runTime: { | |
a4e07f72 JB |
683 | history: expect.any(CircularArray) |
684 | }, | |
685 | waitTime: { | |
a4e07f72 JB |
686 | history: expect.any(CircularArray) |
687 | }, | |
5df69fab JB |
688 | elu: { |
689 | idle: { | |
5df69fab JB |
690 | history: expect.any(CircularArray) |
691 | }, | |
692 | active: { | |
5df69fab | 693 | history: expect.any(CircularArray) |
f7510105 | 694 | } |
5df69fab | 695 | } |
86bf340d | 696 | }) |
465b2940 | 697 | expect(workerNode.usage.tasks.executed).toBeGreaterThan(0) |
b56c2ee5 | 698 | expect(workerNode.usage.tasks.executed).toBeLessThanOrEqual(maxMultiplier) |
b97d82d8 JB |
699 | expect(workerNode.usage.runTime.history.length).toBe(0) |
700 | expect(workerNode.usage.waitTime.history.length).toBe(0) | |
701 | expect(workerNode.usage.elu.idle.history.length).toBe(0) | |
702 | expect(workerNode.usage.elu.active.history.length).toBe(0) | |
9e619829 JB |
703 | } |
704 | pool.setWorkerChoiceStrategy(WorkerChoiceStrategies.FAIR_SHARE) | |
f06e48d8 | 705 | for (const workerNode of pool.workerNodes) { |
465b2940 | 706 | expect(workerNode.usage).toStrictEqual({ |
a4e07f72 JB |
707 | tasks: { |
708 | executed: 0, | |
709 | executing: 0, | |
710 | queued: 0, | |
df593701 | 711 | maxQueued: 0, |
a4e07f72 JB |
712 | failed: 0 |
713 | }, | |
714 | runTime: { | |
a4e07f72 JB |
715 | history: expect.any(CircularArray) |
716 | }, | |
717 | waitTime: { | |
a4e07f72 JB |
718 | history: expect.any(CircularArray) |
719 | }, | |
5df69fab JB |
720 | elu: { |
721 | idle: { | |
5df69fab JB |
722 | history: expect.any(CircularArray) |
723 | }, | |
724 | active: { | |
5df69fab | 725 | history: expect.any(CircularArray) |
f7510105 | 726 | } |
5df69fab | 727 | } |
86bf340d | 728 | }) |
465b2940 JB |
729 | expect(workerNode.usage.runTime.history.length).toBe(0) |
730 | expect(workerNode.usage.waitTime.history.length).toBe(0) | |
b97d82d8 JB |
731 | expect(workerNode.usage.elu.idle.history.length).toBe(0) |
732 | expect(workerNode.usage.elu.active.history.length).toBe(0) | |
ee11a4a2 | 733 | } |
fd7ebd49 | 734 | await pool.destroy() |
ee11a4a2 JB |
735 | }) |
736 | ||
164d950a JB |
737 | it("Verify that pool event emitter 'full' event can register a callback", async () => { |
738 | const pool = new DynamicThreadPool( | |
2431bdb4 | 739 | Math.floor(numberOfWorkers / 2), |
164d950a JB |
740 | numberOfWorkers, |
741 | './tests/worker-files/thread/testWorker.js' | |
742 | ) | |
09c2d0d3 | 743 | const promises = new Set() |
164d950a | 744 | let poolFull = 0 |
d46660cd | 745 | let poolInfo |
8ebe6c30 | 746 | pool.emitter.on(PoolEvents.full, (info) => { |
d46660cd JB |
747 | ++poolFull |
748 | poolInfo = info | |
749 | }) | |
164d950a | 750 | for (let i = 0; i < numberOfWorkers * 2; i++) { |
f5d14e90 | 751 | promises.add(pool.execute()) |
164d950a JB |
752 | } |
753 | await Promise.all(promises) | |
2431bdb4 JB |
754 | // The `full` event is triggered when the number of submitted tasks at once reach the maximum number of workers in the dynamic pool. |
755 | // So in total numberOfWorkers * 2 - 1 times for a loop submitting up to numberOfWorkers * 2 tasks to the dynamic pool with min = (max = numberOfWorkers) / 2. | |
756 | expect(poolFull).toBe(numberOfWorkers * 2 - 1) | |
d46660cd | 757 | expect(poolInfo).toStrictEqual({ |
23ccf9d7 | 758 | version, |
d46660cd JB |
759 | type: PoolTypes.dynamic, |
760 | worker: WorkerTypes.thread, | |
2431bdb4 JB |
761 | ready: expect.any(Boolean), |
762 | strategy: WorkerChoiceStrategies.ROUND_ROBIN, | |
763 | minSize: expect.any(Number), | |
764 | maxSize: expect.any(Number), | |
765 | workerNodes: expect.any(Number), | |
766 | idleWorkerNodes: expect.any(Number), | |
767 | busyWorkerNodes: expect.any(Number), | |
768 | executedTasks: expect.any(Number), | |
769 | executingTasks: expect.any(Number), | |
2431bdb4 JB |
770 | failedTasks: expect.any(Number) |
771 | }) | |
772 | await pool.destroy() | |
773 | }) | |
774 | ||
775 | it("Verify that pool event emitter 'ready' event can register a callback", async () => { | |
d5024c00 JB |
776 | const pool = new DynamicClusterPool( |
777 | Math.floor(numberOfWorkers / 2), | |
2431bdb4 JB |
778 | numberOfWorkers, |
779 | './tests/worker-files/cluster/testWorker.js' | |
780 | ) | |
2431bdb4 | 781 | let poolInfo |
cc3ab78b | 782 | let poolReady = 0 |
8ebe6c30 | 783 | pool.emitter.on(PoolEvents.ready, (info) => { |
2431bdb4 JB |
784 | ++poolReady |
785 | poolInfo = info | |
786 | }) | |
787 | await waitPoolEvents(pool, PoolEvents.ready, 1) | |
788 | expect(poolReady).toBe(1) | |
789 | expect(poolInfo).toStrictEqual({ | |
790 | version, | |
d5024c00 | 791 | type: PoolTypes.dynamic, |
2431bdb4 JB |
792 | worker: WorkerTypes.cluster, |
793 | ready: true, | |
794 | strategy: WorkerChoiceStrategies.ROUND_ROBIN, | |
d46660cd JB |
795 | minSize: expect.any(Number), |
796 | maxSize: expect.any(Number), | |
797 | workerNodes: expect.any(Number), | |
798 | idleWorkerNodes: expect.any(Number), | |
799 | busyWorkerNodes: expect.any(Number), | |
a4e07f72 JB |
800 | executedTasks: expect.any(Number), |
801 | executingTasks: expect.any(Number), | |
a4e07f72 | 802 | failedTasks: expect.any(Number) |
d46660cd | 803 | }) |
164d950a JB |
804 | await pool.destroy() |
805 | }) | |
806 | ||
cf597bc5 | 807 | it("Verify that pool event emitter 'busy' event can register a callback", async () => { |
7c0ba920 JB |
808 | const pool = new FixedThreadPool( |
809 | numberOfWorkers, | |
810 | './tests/worker-files/thread/testWorker.js' | |
811 | ) | |
09c2d0d3 | 812 | const promises = new Set() |
7c0ba920 | 813 | let poolBusy = 0 |
d46660cd | 814 | let poolInfo |
8ebe6c30 | 815 | pool.emitter.on(PoolEvents.busy, (info) => { |
d46660cd JB |
816 | ++poolBusy |
817 | poolInfo = info | |
818 | }) | |
7c0ba920 | 819 | for (let i = 0; i < numberOfWorkers * 2; i++) { |
f5d14e90 | 820 | promises.add(pool.execute()) |
7c0ba920 | 821 | } |
cf597bc5 | 822 | await Promise.all(promises) |
14916bf9 JB |
823 | // The `busy` event is triggered when the number of submitted tasks at once reach the number of fixed pool workers. |
824 | // So in total numberOfWorkers + 1 times for a loop submitting up to numberOfWorkers * 2 tasks to the fixed pool. | |
825 | expect(poolBusy).toBe(numberOfWorkers + 1) | |
d46660cd | 826 | expect(poolInfo).toStrictEqual({ |
23ccf9d7 | 827 | version, |
d46660cd JB |
828 | type: PoolTypes.fixed, |
829 | worker: WorkerTypes.thread, | |
2431bdb4 JB |
830 | ready: expect.any(Boolean), |
831 | strategy: WorkerChoiceStrategies.ROUND_ROBIN, | |
d46660cd JB |
832 | minSize: expect.any(Number), |
833 | maxSize: expect.any(Number), | |
834 | workerNodes: expect.any(Number), | |
835 | idleWorkerNodes: expect.any(Number), | |
836 | busyWorkerNodes: expect.any(Number), | |
a4e07f72 JB |
837 | executedTasks: expect.any(Number), |
838 | executingTasks: expect.any(Number), | |
a4e07f72 | 839 | failedTasks: expect.any(Number) |
d46660cd | 840 | }) |
fd7ebd49 | 841 | await pool.destroy() |
7c0ba920 | 842 | }) |
70a4f5ea | 843 | |
90d7d101 JB |
844 | it('Verify that listTaskFunctions() is working', async () => { |
845 | const dynamicThreadPool = new DynamicThreadPool( | |
846 | Math.floor(numberOfWorkers / 2), | |
847 | numberOfWorkers, | |
848 | './tests/worker-files/thread/testMultipleTaskFunctionsWorker.js' | |
849 | ) | |
850 | await waitPoolEvents(dynamicThreadPool, PoolEvents.ready, 1) | |
851 | expect(dynamicThreadPool.listTaskFunctions()).toStrictEqual([ | |
852 | 'default', | |
853 | 'jsonIntegerSerialization', | |
854 | 'factorial', | |
855 | 'fibonacci' | |
856 | ]) | |
857 | const fixedClusterPool = new FixedClusterPool( | |
858 | numberOfWorkers, | |
859 | './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.js' | |
860 | ) | |
861 | await waitPoolEvents(fixedClusterPool, PoolEvents.ready, 1) | |
862 | expect(fixedClusterPool.listTaskFunctions()).toStrictEqual([ | |
863 | 'default', | |
864 | 'jsonIntegerSerialization', | |
865 | 'factorial', | |
866 | 'fibonacci' | |
867 | ]) | |
868 | }) | |
869 | ||
870 | it('Verify that multiple task functions worker is working', async () => { | |
70a4f5ea | 871 | const pool = new DynamicClusterPool( |
2431bdb4 | 872 | Math.floor(numberOfWorkers / 2), |
70a4f5ea | 873 | numberOfWorkers, |
90d7d101 | 874 | './tests/worker-files/cluster/testMultipleTaskFunctionsWorker.js' |
70a4f5ea JB |
875 | ) |
876 | const data = { n: 10 } | |
82888165 | 877 | const result0 = await pool.execute(data) |
30b963d4 | 878 | expect(result0).toStrictEqual({ ok: 1 }) |
70a4f5ea | 879 | const result1 = await pool.execute(data, 'jsonIntegerSerialization') |
30b963d4 | 880 | expect(result1).toStrictEqual({ ok: 1 }) |
70a4f5ea JB |
881 | const result2 = await pool.execute(data, 'factorial') |
882 | expect(result2).toBe(3628800) | |
883 | const result3 = await pool.execute(data, 'fibonacci') | |
024daf59 | 884 | expect(result3).toBe(55) |
70a4f5ea | 885 | }) |
3ec964d6 | 886 | }) |