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