poolifier 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -11
- package/lib/index.d.ts +145 -58
- package/lib/index.js +1 -1
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
<img alt="Javascript Standard Style Guide" src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"></a>
|
|
22
22
|
<a href="https://gitter.im/poolifier/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge">
|
|
23
23
|
<img alt="Gitter chat" src="https://badges.gitter.im/poolifier/community.svg"></a>
|
|
24
|
-
<a href="https://badgen.net/
|
|
25
|
-
<img alt="Dependabot" src="https://badgen.net/
|
|
24
|
+
<a href="https://badgen.net/badge/Dependabot/enabled/green?icon=dependabot">
|
|
25
|
+
<img alt="Dependabot" src="https://badgen.net/badge/Dependabot/enabled/green?icon=dependabot"></a>
|
|
26
26
|
<a href="http://makeapullrequest.com">
|
|
27
27
|
<img alt="PR Welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square"></a>
|
|
28
28
|
<a href="https://img.shields.io/static/v1?label=dependencies&message=no%20dependencies&color=brightgreen">
|
|
@@ -146,7 +146,7 @@ Remember that workers can only send and receive serializable data.
|
|
|
146
146
|
|
|
147
147
|
## Node versions
|
|
148
148
|
|
|
149
|
-
You can use node versions 12.x
|
|
149
|
+
You can use node versions >= 12.x for thread pool, and node versions >= 16.x for cluster pool.
|
|
150
150
|
|
|
151
151
|
## API
|
|
152
152
|
|
|
@@ -156,14 +156,18 @@ You can use node versions 12.x, 13.x, 14.x, 16.x
|
|
|
156
156
|
`filePath` (mandatory) Path to a file with a worker implementation
|
|
157
157
|
`opts` (optional) An object with these properties:
|
|
158
158
|
|
|
159
|
+
- `messageHandler` (optional) - A function that will listen for message event on each worker
|
|
159
160
|
- `errorHandler` (optional) - A function that will listen for error event on each worker
|
|
160
161
|
- `onlineHandler` (optional) - A function that will listen for online event on each worker
|
|
161
162
|
- `exitHandler` (optional) - A function that will listen for exit event on each worker
|
|
162
|
-
- `workerChoiceStrategy` (optional) - The
|
|
163
|
+
- `workerChoiceStrategy` (optional) - The worker choice strategy to use in this pool:
|
|
163
164
|
|
|
164
|
-
- `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in
|
|
165
|
-
- `WorkerChoiceStrategies.LESS_RECENTLY_USED`: Submit tasks to the less recently used worker
|
|
165
|
+
- `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in a round robbin fashion
|
|
166
|
+
- `WorkerChoiceStrategies.LESS_RECENTLY_USED`: Submit tasks to the less recently used worker
|
|
167
|
+
- `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` Submit tasks to worker using a weighted round robin scheduling algorithm based on tasks execution time
|
|
168
|
+
- `WorkerChoiceStrategies.FAIR_SHARE`: Submit tasks to worker using a fair share tasks scheduling algorithm based on tasks execution time
|
|
166
169
|
|
|
170
|
+
`WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks
|
|
167
171
|
Default: `WorkerChoiceStrategies.ROUND_ROBIN`
|
|
168
172
|
|
|
169
173
|
- `enableEvents` (optional) - Events emission enablement in this pool. Default: true
|
|
@@ -194,9 +198,9 @@ This method will call the terminate method on each worker.
|
|
|
194
198
|
The last active time of your worker unit will be updated when a task is submitted to a worker or when a worker terminate a task.
|
|
195
199
|
If `killBehavior` is set to `KillBehaviors.HARD` this value represents also the timeout for the tasks that you submit to the pool, when this timeout expires your tasks is interrupted and the worker is killed if is not part of the minimum size of the pool.
|
|
196
200
|
If `killBehavior` is set to `KillBehaviors.SOFT` your tasks have no timeout and your workers will not be terminated until your task is completed.
|
|
197
|
-
Default:
|
|
201
|
+
Default: 60000 ms
|
|
198
202
|
|
|
199
|
-
- `async` - true/false, true if your function contains async pieces else false
|
|
203
|
+
- `async` - true/false, true if your function contains async code pieces, else false
|
|
200
204
|
- `killBehavior` - Dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
|
|
201
205
|
**KillBehaviors.SOFT**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted.
|
|
202
206
|
**KillBehaviors.HARD**: If `lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
|
|
@@ -244,13 +248,11 @@ But in general, **always profile your application**
|
|
|
244
248
|
## Contribute
|
|
245
249
|
|
|
246
250
|
See guidelines [CONTRIBUTING](CONTRIBUTING.md)
|
|
247
|
-
Choose your task here [2.
|
|
251
|
+
Choose your task here [2.3.0](https://github.com/orgs/poolifier/projects/1), propose an idea, a fix, an improvement.
|
|
248
252
|
|
|
249
253
|
## Team
|
|
250
254
|
|
|
251
255
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
252
|
-
<!-- prettier-ignore-start -->
|
|
253
|
-
<!-- markdownlint-disable -->
|
|
254
256
|
|
|
255
257
|
**Creator/Owner:**
|
|
256
258
|
|
package/lib/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ interface WorkerOptions {
|
|
|
30
30
|
* when this timeout expires your tasks is interrupted and the worker is killed if is not part of the minimum size of the pool.
|
|
31
31
|
* - If `killBehavior` is set to `KillBehaviors.SOFT` your tasks have no timeout and your workers will not be terminated until your task is completed.
|
|
32
32
|
*
|
|
33
|
-
* @default
|
|
33
|
+
* @default 60000 ms
|
|
34
34
|
*/
|
|
35
35
|
maxInactiveTime?: number;
|
|
36
36
|
/**
|
|
@@ -77,6 +77,10 @@ interface MessageValue<Data = unknown, MainWorker extends ClusterWorker | Messag
|
|
|
77
77
|
* Error.
|
|
78
78
|
*/
|
|
79
79
|
readonly error?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Task runtime.
|
|
82
|
+
*/
|
|
83
|
+
readonly taskRunTime?: number;
|
|
80
84
|
/**
|
|
81
85
|
* Reference to main worker.
|
|
82
86
|
*
|
|
@@ -90,7 +94,7 @@ interface MessageValue<Data = unknown, MainWorker extends ClusterWorker | Messag
|
|
|
90
94
|
* @template Worker Type of worker.
|
|
91
95
|
* @template Response Type of response of execution. This can only be serializable data.
|
|
92
96
|
*/
|
|
93
|
-
interface PromiseWorkerResponseWrapper<Worker extends
|
|
97
|
+
interface PromiseWorkerResponseWrapper<Worker extends IPoolWorker, Response = unknown> {
|
|
94
98
|
/**
|
|
95
99
|
* Resolve callback to fulfill the promise.
|
|
96
100
|
*/
|
|
@@ -121,13 +125,9 @@ type OnlineHandler<Worker> = (this: Worker) => void;
|
|
|
121
125
|
*/
|
|
122
126
|
type ExitHandler<Worker> = (this: Worker, code: number) => void;
|
|
123
127
|
/**
|
|
124
|
-
*
|
|
128
|
+
* Interface that describes the minimum required implementation of listener events for a pool worker.
|
|
125
129
|
*/
|
|
126
130
|
interface IPoolWorker {
|
|
127
|
-
/**
|
|
128
|
-
* Worker identifier.
|
|
129
|
-
*/
|
|
130
|
-
readonly id?: number;
|
|
131
131
|
/**
|
|
132
132
|
* Register a listener to the message event.
|
|
133
133
|
*
|
|
@@ -164,32 +164,48 @@ interface IPoolWorker {
|
|
|
164
164
|
*/
|
|
165
165
|
once(event: "exit", handler: ExitHandler<this>): void;
|
|
166
166
|
}
|
|
167
|
-
/**
|
|
168
|
-
* Basic class that implement the minimum required for a pool worker.
|
|
169
|
-
*/
|
|
170
|
-
declare abstract class AbstractPoolWorker implements IPoolWorker {
|
|
171
|
-
/** @inheritDoc */
|
|
172
|
-
abstract on(event: "message", handler: MessageHandler<this>): void;
|
|
173
|
-
/** @inheritDoc */
|
|
174
|
-
abstract on(event: "error", handler: ErrorHandler<this>): void;
|
|
175
|
-
/** @inheritDoc */
|
|
176
|
-
abstract on(event: "online", handler: OnlineHandler<this>): void;
|
|
177
|
-
/** @inheritDoc */
|
|
178
|
-
abstract on(event: "exit", handler: ExitHandler<this>): void;
|
|
179
|
-
/** @inheritDoc */
|
|
180
|
-
abstract once(event: "exit", handler: ExitHandler<this>): void;
|
|
181
|
-
}
|
|
182
167
|
/**
|
|
183
168
|
* Enumeration of worker choice strategies.
|
|
184
169
|
*/
|
|
185
170
|
declare const WorkerChoiceStrategies: Readonly<{
|
|
186
171
|
readonly ROUND_ROBIN: "ROUND_ROBIN";
|
|
187
172
|
readonly LESS_RECENTLY_USED: "LESS_RECENTLY_USED";
|
|
173
|
+
readonly FAIR_SHARE: "FAIR_SHARE";
|
|
174
|
+
readonly WEIGHTED_ROUND_ROBIN: "WEIGHTED_ROUND_ROBIN";
|
|
188
175
|
}>;
|
|
189
176
|
/**
|
|
190
177
|
* Worker choice strategy.
|
|
191
178
|
*/
|
|
192
179
|
type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies;
|
|
180
|
+
/**
|
|
181
|
+
* Pool tasks usage statistics requirements.
|
|
182
|
+
*/
|
|
183
|
+
type RequiredStatistics = {
|
|
184
|
+
runTime: boolean;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Worker choice strategy interface.
|
|
188
|
+
*
|
|
189
|
+
* @template Worker Type of worker which manages the strategy.
|
|
190
|
+
*/
|
|
191
|
+
interface IWorkerChoiceStrategy<Worker extends IPoolWorker> {
|
|
192
|
+
/**
|
|
193
|
+
* Is the pool attached to the strategy dynamic?.
|
|
194
|
+
*/
|
|
195
|
+
readonly isDynamicPool: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* Required pool tasks usage statistics.
|
|
198
|
+
*/
|
|
199
|
+
readonly requiredStatistics: RequiredStatistics;
|
|
200
|
+
/**
|
|
201
|
+
* Resets strategy internals (counters, statistics, etc.).
|
|
202
|
+
*/
|
|
203
|
+
reset(): boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Chooses a worker in the pool.
|
|
206
|
+
*/
|
|
207
|
+
choose(): Worker;
|
|
208
|
+
}
|
|
193
209
|
/**
|
|
194
210
|
* Options for a poolifier pool.
|
|
195
211
|
*/
|
|
@@ -211,7 +227,7 @@ interface PoolOptions<Worker> {
|
|
|
211
227
|
*/
|
|
212
228
|
exitHandler?: ExitHandler<Worker>;
|
|
213
229
|
/**
|
|
214
|
-
* The
|
|
230
|
+
* The worker choice strategy to use in this pool.
|
|
215
231
|
*/
|
|
216
232
|
workerChoiceStrategy?: WorkerChoiceStrategy;
|
|
217
233
|
/**
|
|
@@ -229,18 +245,18 @@ interface PoolOptions<Worker> {
|
|
|
229
245
|
*/
|
|
230
246
|
interface IPool<Data = unknown, Response = unknown> {
|
|
231
247
|
/**
|
|
232
|
-
*
|
|
248
|
+
* Performs the task specified in the constructor with the data parameter.
|
|
233
249
|
*
|
|
234
250
|
* @param data The input for the specified task. This can only be serializable data.
|
|
235
251
|
* @returns Promise that will be resolved when the task is successfully completed.
|
|
236
252
|
*/
|
|
237
253
|
execute(data: Data): Promise<Response>;
|
|
238
254
|
/**
|
|
239
|
-
*
|
|
255
|
+
* Shutdowns every current worker in this pool.
|
|
240
256
|
*/
|
|
241
257
|
destroy(): Promise<void>;
|
|
242
258
|
/**
|
|
243
|
-
*
|
|
259
|
+
* Sets the worker choice strategy in this pool.
|
|
244
260
|
*
|
|
245
261
|
* @param workerChoiceStrategy The worker choice strategy.
|
|
246
262
|
*/
|
|
@@ -253,6 +269,15 @@ declare enum PoolType {
|
|
|
253
269
|
FIXED = "fixed",
|
|
254
270
|
DYNAMIC = "dynamic"
|
|
255
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Tasks usage statistics.
|
|
274
|
+
*/
|
|
275
|
+
interface TasksUsage {
|
|
276
|
+
run: number;
|
|
277
|
+
running: number;
|
|
278
|
+
runTime: number;
|
|
279
|
+
avgRunTime: number;
|
|
280
|
+
}
|
|
256
281
|
/**
|
|
257
282
|
* Internal poolifier pool emitter.
|
|
258
283
|
*/
|
|
@@ -265,18 +290,18 @@ declare class PoolEmitter extends EventEmitter {
|
|
|
265
290
|
* @template Data Type of data sent to the worker.
|
|
266
291
|
* @template Response Type of response of execution.
|
|
267
292
|
*/
|
|
268
|
-
interface IPoolInternal<Worker extends
|
|
293
|
+
interface IPoolInternal<Worker extends IPoolWorker, Data = unknown, Response = unknown> extends IPool<Data, Response> {
|
|
269
294
|
/**
|
|
270
295
|
* List of currently available workers.
|
|
271
296
|
*/
|
|
272
297
|
readonly workers: Worker[];
|
|
273
298
|
/**
|
|
274
|
-
* The tasks map.
|
|
299
|
+
* The workers tasks usage map.
|
|
275
300
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
301
|
+
* `key`: The `Worker`
|
|
302
|
+
* `value`: Worker tasks usage statistics.
|
|
278
303
|
*/
|
|
279
|
-
readonly
|
|
304
|
+
readonly workersTasksUsage: Map<Worker, TasksUsage>;
|
|
280
305
|
/**
|
|
281
306
|
* Emitter on which events can be listened to.
|
|
282
307
|
*
|
|
@@ -329,6 +354,13 @@ interface IPoolInternal<Worker extends AbstractPoolWorker, Data = unknown, Respo
|
|
|
329
354
|
* @returns The number of tasks currently running on the worker.
|
|
330
355
|
*/
|
|
331
356
|
getWorkerRunningTasks(worker: Worker): number | undefined;
|
|
357
|
+
/**
|
|
358
|
+
* Get worker average tasks runtime.
|
|
359
|
+
*
|
|
360
|
+
* @param worker The worker.
|
|
361
|
+
* @returns The average tasks runtime on the worker.
|
|
362
|
+
*/
|
|
363
|
+
getWorkerAverageTasksRunTime(worker: Worker): number | undefined;
|
|
332
364
|
}
|
|
333
365
|
/**
|
|
334
366
|
* The worker choice strategy context.
|
|
@@ -337,7 +369,7 @@ interface IPoolInternal<Worker extends AbstractPoolWorker, Data = unknown, Respo
|
|
|
337
369
|
* @template Data Type of data sent to the worker. This can only be serializable data.
|
|
338
370
|
* @template Response Type of response of execution. This can only be serializable data.
|
|
339
371
|
*/
|
|
340
|
-
declare class WorkerChoiceStrategyContext<Worker extends
|
|
372
|
+
declare class WorkerChoiceStrategyContext<Worker extends IPoolWorker, Data, Response> {
|
|
341
373
|
private readonly pool;
|
|
342
374
|
private createDynamicallyWorkerCallback;
|
|
343
375
|
private workerChoiceStrategy;
|
|
@@ -350,40 +382,46 @@ declare class WorkerChoiceStrategyContext<Worker extends AbstractPoolWorker, Dat
|
|
|
350
382
|
*/
|
|
351
383
|
constructor(pool: IPoolInternal<Worker, Data, Response>, createDynamicallyWorkerCallback: () => Worker, workerChoiceStrategy?: WorkerChoiceStrategy);
|
|
352
384
|
/**
|
|
353
|
-
*
|
|
385
|
+
* Gets the worker choice strategy instance specific to the pool type.
|
|
354
386
|
*
|
|
355
387
|
* @param workerChoiceStrategy The worker choice strategy.
|
|
356
388
|
* @returns The worker choice strategy instance for the pool type.
|
|
357
389
|
*/
|
|
358
390
|
private getPoolWorkerChoiceStrategy;
|
|
359
391
|
/**
|
|
360
|
-
*
|
|
392
|
+
* Gets the worker choice strategy used in the context.
|
|
393
|
+
*
|
|
394
|
+
* @returns The worker choice strategy.
|
|
395
|
+
*/
|
|
396
|
+
getWorkerChoiceStrategy(): IWorkerChoiceStrategy<Worker>;
|
|
397
|
+
/**
|
|
398
|
+
* Sets the worker choice strategy to use in the context.
|
|
361
399
|
*
|
|
362
400
|
* @param workerChoiceStrategy The worker choice strategy to set.
|
|
363
401
|
*/
|
|
364
402
|
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
|
|
365
403
|
/**
|
|
366
|
-
*
|
|
404
|
+
* Chooses a worker with the underlying selection strategy.
|
|
367
405
|
*
|
|
368
406
|
* @returns The chosen one.
|
|
369
407
|
*/
|
|
370
408
|
execute(): Worker;
|
|
371
409
|
}
|
|
372
410
|
/**
|
|
373
|
-
* Base class
|
|
411
|
+
* Base class that implements some shared logic for all poolifier pools.
|
|
374
412
|
*
|
|
375
413
|
* @template Worker Type of worker which manages this pool.
|
|
376
414
|
* @template Data Type of data sent to the worker. This can only be serializable data.
|
|
377
415
|
* @template Response Type of response of execution. This can only be serializable data.
|
|
378
416
|
*/
|
|
379
|
-
declare abstract class AbstractPool<Worker extends
|
|
417
|
+
declare abstract class AbstractPool<Worker extends IPoolWorker, Data = unknown, Response = unknown> implements IPoolInternal<Worker, Data, Response> {
|
|
380
418
|
readonly numberOfWorkers: number;
|
|
381
419
|
readonly filePath: string;
|
|
382
420
|
readonly opts: PoolOptions<Worker>;
|
|
383
421
|
/** @inheritDoc */
|
|
384
422
|
readonly workers: Worker[];
|
|
385
423
|
/** @inheritDoc */
|
|
386
|
-
readonly
|
|
424
|
+
readonly workersTasksUsage: Map<Worker, TasksUsage>;
|
|
387
425
|
/** @inheritDoc */
|
|
388
426
|
readonly emitter?: PoolEmitter;
|
|
389
427
|
/** @inheritDoc */
|
|
@@ -423,9 +461,11 @@ declare abstract class AbstractPool<Worker extends AbstractPoolWorker, Data = un
|
|
|
423
461
|
/** @inheritDoc */
|
|
424
462
|
get numberOfRunningTasks(): number;
|
|
425
463
|
/** @inheritDoc */
|
|
464
|
+
getWorkerIndex(worker: Worker): number;
|
|
465
|
+
/** @inheritDoc */
|
|
426
466
|
getWorkerRunningTasks(worker: Worker): number | undefined;
|
|
427
467
|
/** @inheritDoc */
|
|
428
|
-
|
|
468
|
+
getWorkerAverageTasksRunTime(worker: Worker): number | undefined;
|
|
429
469
|
/** @inheritDoc */
|
|
430
470
|
setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
|
|
431
471
|
/** @inheritDoc */
|
|
@@ -438,7 +478,7 @@ declare abstract class AbstractPool<Worker extends AbstractPoolWorker, Data = un
|
|
|
438
478
|
/** @inheritDoc */
|
|
439
479
|
destroy(): Promise<void>;
|
|
440
480
|
/**
|
|
441
|
-
*
|
|
481
|
+
* Shutdowns given worker.
|
|
442
482
|
*
|
|
443
483
|
* @param worker A worker within `workers`.
|
|
444
484
|
*/
|
|
@@ -453,24 +493,20 @@ declare abstract class AbstractPool<Worker extends AbstractPoolWorker, Data = un
|
|
|
453
493
|
*/
|
|
454
494
|
protected abstract isMain(): boolean;
|
|
455
495
|
/**
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
* @param worker Worker whose tasks are increased.
|
|
459
|
-
*/
|
|
460
|
-
protected increaseWorkersTask(worker: Worker): void;
|
|
461
|
-
/**
|
|
462
|
-
* Decrease the number of tasks that the given worker has applied.
|
|
496
|
+
* Hook executed before the worker task promise resolution.
|
|
497
|
+
* Can be overridden.
|
|
463
498
|
*
|
|
464
|
-
* @param worker
|
|
499
|
+
* @param worker The worker.
|
|
465
500
|
*/
|
|
466
|
-
protected
|
|
501
|
+
protected beforePromiseWorkerResponseHook(worker: Worker): void;
|
|
467
502
|
/**
|
|
468
|
-
*
|
|
503
|
+
* Hook executed after the worker task promise resolution.
|
|
504
|
+
* Can be overridden.
|
|
469
505
|
*
|
|
470
|
-
* @param
|
|
471
|
-
* @param
|
|
506
|
+
* @param message The received message.
|
|
507
|
+
* @param promise The Promise response.
|
|
472
508
|
*/
|
|
473
|
-
|
|
509
|
+
protected afterPromiseWorkerResponseHook(message: MessageValue<Response>, promise: PromiseWorkerResponseWrapper<Worker, Response>): void;
|
|
474
510
|
/**
|
|
475
511
|
* Removes the given worker from the pool.
|
|
476
512
|
*
|
|
@@ -478,7 +514,7 @@ declare abstract class AbstractPool<Worker extends AbstractPoolWorker, Data = un
|
|
|
478
514
|
*/
|
|
479
515
|
protected removeWorker(worker: Worker): void;
|
|
480
516
|
/**
|
|
481
|
-
*
|
|
517
|
+
* Chooses a worker for the next task.
|
|
482
518
|
*
|
|
483
519
|
* The default implementation uses a round robin algorithm to distribute the load.
|
|
484
520
|
*
|
|
@@ -486,14 +522,14 @@ declare abstract class AbstractPool<Worker extends AbstractPoolWorker, Data = un
|
|
|
486
522
|
*/
|
|
487
523
|
protected chooseWorker(): Worker;
|
|
488
524
|
/**
|
|
489
|
-
*
|
|
525
|
+
* Sends a message to the given worker.
|
|
490
526
|
*
|
|
491
527
|
* @param worker The worker which should receive the message.
|
|
492
528
|
* @param message The message.
|
|
493
529
|
*/
|
|
494
530
|
protected abstract sendToWorker(worker: Worker, message: MessageValue<Data>): void;
|
|
495
531
|
/**
|
|
496
|
-
*
|
|
532
|
+
* Registers a listener callback on a given worker.
|
|
497
533
|
*
|
|
498
534
|
* @param worker A worker.
|
|
499
535
|
* @param listener A message listener callback.
|
|
@@ -525,6 +561,57 @@ declare abstract class AbstractPool<Worker extends AbstractPoolWorker, Data = un
|
|
|
525
561
|
*/
|
|
526
562
|
protected workerListener(): (message: MessageValue<Response>) => void;
|
|
527
563
|
private checkAndEmitBusy;
|
|
564
|
+
/**
|
|
565
|
+
* Increases the number of tasks that the given worker has applied.
|
|
566
|
+
*
|
|
567
|
+
* @param worker Worker which running tasks is increased.
|
|
568
|
+
*/
|
|
569
|
+
private increaseWorkerRunningTasks;
|
|
570
|
+
/**
|
|
571
|
+
* Decreases the number of tasks that the given worker has applied.
|
|
572
|
+
*
|
|
573
|
+
* @param worker Worker which running tasks is decreased.
|
|
574
|
+
*/
|
|
575
|
+
private decreaseWorkerRunningTasks;
|
|
576
|
+
/**
|
|
577
|
+
* Steps the number of tasks that the given worker has applied.
|
|
578
|
+
*
|
|
579
|
+
* @param worker Worker which running tasks are stepped.
|
|
580
|
+
* @param step Number of running tasks step.
|
|
581
|
+
*/
|
|
582
|
+
private stepWorkerRunningTasks;
|
|
583
|
+
/**
|
|
584
|
+
* Steps the number of tasks that the given worker has run.
|
|
585
|
+
*
|
|
586
|
+
* @param worker Worker which has run tasks.
|
|
587
|
+
* @param step Number of run tasks step.
|
|
588
|
+
*/
|
|
589
|
+
private stepWorkerRunTasks;
|
|
590
|
+
/**
|
|
591
|
+
* Updates tasks runtime for the given worker.
|
|
592
|
+
*
|
|
593
|
+
* @param worker Worker which run the task.
|
|
594
|
+
* @param taskRunTime Worker task runtime.
|
|
595
|
+
*/
|
|
596
|
+
private updateWorkerTasksRunTime;
|
|
597
|
+
/**
|
|
598
|
+
* Initializes tasks usage statistics.
|
|
599
|
+
*
|
|
600
|
+
* @param worker The worker.
|
|
601
|
+
*/
|
|
602
|
+
initWorkerTasksUsage(worker: Worker): void;
|
|
603
|
+
/**
|
|
604
|
+
* Removes worker tasks usage statistics.
|
|
605
|
+
*
|
|
606
|
+
* @param worker The worker.
|
|
607
|
+
*/
|
|
608
|
+
private removeWorkerTasksUsage;
|
|
609
|
+
/**
|
|
610
|
+
* Resets worker tasks usage statistics.
|
|
611
|
+
*
|
|
612
|
+
* @param worker The worker.
|
|
613
|
+
*/
|
|
614
|
+
private resetWorkerTasksUsage;
|
|
528
615
|
}
|
|
529
616
|
/**
|
|
530
617
|
* Options for a poolifier cluster pool.
|
|
@@ -676,7 +763,7 @@ declare class DynamicThreadPool<Data = unknown, Response = unknown> extends Fixe
|
|
|
676
763
|
get busy(): boolean;
|
|
677
764
|
}
|
|
678
765
|
/**
|
|
679
|
-
* Base class
|
|
766
|
+
* Base class that implements some shared logic for all poolifier workers.
|
|
680
767
|
*
|
|
681
768
|
* @template MainWorker Type of main worker.
|
|
682
769
|
* @template Data Type of data this worker receives from pool's execution. This can only be serializable data.
|
|
@@ -805,4 +892,4 @@ declare class ThreadWorker<Data = unknown, Response = unknown> extends AbstractW
|
|
|
805
892
|
protected sendToMainWorker(message: MessageValue<Response>): void;
|
|
806
893
|
}
|
|
807
894
|
export { DynamicClusterPool, FixedClusterPool, WorkerChoiceStrategies, DynamicThreadPool, FixedThreadPool, AbstractWorker, ClusterWorker$0 as ClusterWorker, ThreadWorker, KillBehaviors };
|
|
808
|
-
export type { ClusterPoolOptions, IPool, PoolOptions, ErrorHandler, ExitHandler, IPoolWorker, OnlineHandler, WorkerChoiceStrategy, ThreadWorkerWithMessageChannel, KillBehavior, WorkerOptions };
|
|
895
|
+
export type { ClusterPoolOptions, IPool, PoolOptions, ErrorHandler, ExitHandler, IPoolWorker, MessageHandler, OnlineHandler, WorkerChoiceStrategy, ThreadWorkerWithMessageChannel, KillBehavior, WorkerOptions };
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("events"),r=require("cluster"),t=require("worker_threads"),s=require("async_hooks");function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i,n=o(e),a=o(r);!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(i||(i={}));class h extends n.default{}const c=()=>{},l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const k=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED"});class u{constructor(e){this.pool=e,this.isDynamicPool=this.pool.type===i.DYNAMIC}}class p extends u{choose(){let e,r=1/0;for(const t of this.pool.workers){const s=this.pool.getWorkerRunningTasks(t);if(!1===this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class d extends u{constructor(){super(...arguments),this.nextWorkerIndex=0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.nextWorkerIndex===this.pool.workers.length-1?0:this.nextWorkerIndex+1,e}}class W{static getWorkerChoiceStrategy(e,r=k.ROUND_ROBIN){switch(r){case k.ROUND_ROBIN:return new d(e);case k.LESS_RECENTLY_USED:return new p(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}}class g extends u{constructor(e,r,t=k.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=W.getWorkerChoiceStrategy(this.pool,t)}choose(){const e=this.pool.findFreeWorker();return e||(this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback())}}class m{constructor(e,r,t=k.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(e=k.ROUND_ROBIN){return this.pool.type===i.DYNAMIC?new g(this.pool,this.createDynamicallyWorkerCallback,e):W.getWorkerChoiceStrategy(this.pool,e)}setWorkerChoiceStrategy(e){this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class y{constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,this.workers=[],this.tasks=new Map,this.promiseMap=new Map,this.nextMessageId=0,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();this.opts.enableEvents&&(this.emitter=new h),this.workerChoiceStrategyContext=new m(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=l.HARD,(r.kill===t||0===this.getWorkerRunningTasks(e))&&this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(!e)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new Error("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new Error("Cannot instantiate a pool with a negative number of workers");if(this.type===i.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){var r,t;this.opts.workerChoiceStrategy=null!==(r=e.workerChoiceStrategy)&&void 0!==r?r:k.ROUND_ROBIN,this.opts.enableEvents=null===(t=e.enableEvents)||void 0===t||t}get numberOfRunningTasks(){return this.promiseMap.size}getWorkerRunningTasks(e){return this.tasks.get(e)}getWorkerIndex(e){return this.workers.indexOf(e)}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorker()}findFreeWorker(){for(const e of this.workers)if(0===this.getWorkerRunningTasks(e))return e;return!1}execute(e){const r=this.chooseWorker(),t=++this.nextMessageId,s=this.internalExecute(r,t);return this.checkAndEmitBusy(),e=null!=e?e:{},this.sendToWorker(r,{data:e,id:t}),s}async destroy(){await Promise.all(this.workers.map((e=>this.destroyWorker(e))))}setupHook(){}increaseWorkersTask(e){this.stepWorkerNumberOfTasks(e,1)}decreaseWorkersTasks(e){this.stepWorkerNumberOfTasks(e,-1)}stepWorkerNumberOfTasks(e,r){const t=this.tasks.get(e);if(void 0===t)throw Error("Worker could not be found in tasks map");this.tasks.set(e,t+r)}removeWorker(e){this.workers.splice(this.getWorkerIndex(e),1),this.tasks.delete(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}internalExecute(e,r){return this.increaseWorkersTask(e),new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}createAndSetupWorker(){var e,r,t,s;const o=this.createWorker();return o.on("message",null!==(e=this.opts.messageHandler)&&void 0!==e?e:c),o.on("error",null!==(r=this.opts.errorHandler)&&void 0!==r?r:c),o.on("online",null!==(t=this.opts.onlineHandler)&&void 0!==t?t:c),o.on("exit",null!==(s=this.opts.exitHandler)&&void 0!==s?s:c),o.once("exit",(()=>this.removeWorker(o))),this.workers.push(o),this.tasks.set(o,0),this.afterWorkerSetup(o),o}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseMap.get(e.id);void 0!==r&&(this.decreaseWorkersTasks(r.worker),e.error?r.reject(e.error):r.resolve(e.data),this.promiseMap.delete(e.id))}}}checkAndEmitBusy(){var e;this.opts.enableEvents&&this.busy&&(null===(e=this.emitter)||void 0===e||e.emit("busy"))}}class w extends y{constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){a.default.setupPrimary({exec:this.filePath})}isMain(){return a.default.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return a.default.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return i.FIXED}get busy(){return this.internalGetBusyStatus()}}class f extends y{constructor(e,r,t={}){super(e,r,t)}isMain(){return t.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){var t;null===(t=e.port2)||void 0===t||t.on("message",r)}createWorker(){return new t.Worker(this.filePath,{env:t.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:s}=new t.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return i.FIXED}get busy(){return this.internalGetBusyStatus()}}const v=l.SOFT;class x extends s.AsyncResource{constructor(e,r,t,s,o={killBehavior:v,maxInactiveTime:6e4}){var i,n;super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),r||(this.aliveInterval=setInterval(this.checkAlive.bind(this),(null!==(i=this.opts.maxInactiveTime)&&void 0!==i?i:6e4)/2),this.checkAlive.bind(this)()),null===(n=this.mainWorker)||void 0===n||n.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){var r,t;this.opts.killBehavior=null!==(r=e.killBehavior)&&void 0!==r?r:v,this.opts.maxInactiveTime=null!==(t=e.maxInactiveTime)&&void 0!==t?t:6e4,this.opts.async=!!e.async}checkFunctionInput(e){if(!e)throw new Error("fn parameter is mandatory")}getMainWorker(){if(!this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){var e;Date.now()-this.lastTaskTimestamp>(null!==(e=this.opts.maxInactiveTime)&&void 0!==e?e:6e4)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=e(r.data);this.sendToMainWorker({data:t,id:r.id})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,r){e(r.data).then((e=>(this.sendToMainWorker({data:e,id:r.id}),null))).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(c)}}exports.AbstractWorker=x,exports.ClusterWorker=class extends x{constructor(e,r={}){super("worker-cluster-pool:poolifier",a.default.isPrimary,e,a.default.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends w{constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return i.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends f{constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return i.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=w,exports.FixedThreadPool=f,exports.KillBehaviors=l,exports.ThreadWorker=class extends x{constructor(e,r={}){super("worker-thread-pool:poolifier",t.isMainThread,e,t.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=k;
|
|
1
|
+
"use strict";var e,r=require("events"),t=require("cluster"),s=require("os"),o=require("worker_threads"),i=require("async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class n extends r{}const a=()=>{},h=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const k=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_RECENTLY_USED:"LESS_RECENTLY_USED",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class u{constructor(r){this.pool=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.requiredStatistics={runTime:!1}}}class c extends u{constructor(){super(...arguments),this.requiredStatistics={runTime:!0},this.workerLastVirtualTaskTimestamp=new Map}reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){this.computeWorkerLastVirtualTaskTimestamp();let e,r=1/0;for(const t of this.pool.workers){const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}computeWorkerLastVirtualTaskTimestamp(){for(const e of this.pool.workers){const r=Math.max(Date.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),t=r+(this.pool.getWorkerAverageTasksRunTime(e)??0);this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:t})}}}class l extends u{reset(){return!0}choose(){let e,r=1/0;for(const t of this.pool.workers){const s=this.pool.getWorkerRunningTasks(t);if(!1===this.isDynamicPool&&0===s)return t;s<r&&(e=t,r=s)}return e}}class p extends u{constructor(){super(...arguments),this.nextWorkerIndex=0}reset(){return this.nextWorkerIndex=0,!0}choose(){const e=this.pool.workers[this.nextWorkerIndex];return this.nextWorkerIndex=this.nextWorkerIndex===this.pool.workers.length-1?0:this.nextWorkerIndex+1,e}}class W extends u{constructor(e){super(e),this.requiredStatistics={runTime:!0},this.previousWorkerIndex=0,this.currentWorkerIndex=0,this.workersTaskRunTime=new Map,this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.previousWorkerIndex=0,this.currentWorkerIndex=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.pool.workers[this.currentWorkerIndex];!0===this.isDynamicPool&&!1===this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.getWorkerVirtualTaskRunTime(e)??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;if(this.currentWorkerIndex===this.previousWorkerIndex){const s=(this.workersTaskRunTime.get(e)?.runTime??0)+r;this.setWorkerTaskRunTime(e,t,s)}else this.setWorkerTaskRunTime(e,t,0);return r<t?this.previousWorkerIndex=this.currentWorkerIndex:(this.previousWorkerIndex=this.currentWorkerIndex,this.currentWorkerIndex=this.pool.workers.length-1===this.currentWorkerIndex?0:this.currentWorkerIndex+1),this.pool.workers[this.currentWorkerIndex]}initWorkersTaskRunTime(){for(const e of this.pool.workers)this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.pool.getWorkerAverageTasksRunTime(e)}computeWorkerWeight(){let e=0;for(const r of s.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s.cpus().length)}}class g{static getWorkerChoiceStrategy(e,r=k.ROUND_ROBIN){switch(r){case k.ROUND_ROBIN:return new p(e);case k.LESS_RECENTLY_USED:return new l(e);case k.FAIR_SHARE:return new c(e);case k.WEIGHTED_ROUND_ROBIN:return new W(e);default:throw new Error(`Worker choice strategy '${r}' not found`)}}}class d extends u{constructor(e,r,t=k.ROUND_ROBIN){super(e),this.createDynamicallyWorkerCallback=r,this.workerChoiceStrategy=g.getWorkerChoiceStrategy(this.pool,t),this.requiredStatistics=this.workerChoiceStrategy.requiredStatistics}reset(){return this.workerChoiceStrategy.reset()}choose(){const e=this.pool.findFreeWorker();return e||(!0===this.pool.busy?this.workerChoiceStrategy.choose():this.createDynamicallyWorkerCallback())}}class m{constructor(e,r,t=k.ROUND_ROBIN){this.pool=e,this.createDynamicallyWorkerCallback=r,this.setWorkerChoiceStrategy(t)}getPoolWorkerChoiceStrategy(r=k.ROUND_ROBIN){return this.pool.type===e.DYNAMIC?new d(this.pool,this.createDynamicallyWorkerCallback,r):g.getWorkerChoiceStrategy(this.pool,r)}getWorkerChoiceStrategy(){return this.workerChoiceStrategy}setWorkerChoiceStrategy(e){this.workerChoiceStrategy?.reset(),this.workerChoiceStrategy=this.getPoolWorkerChoiceStrategy(e)}execute(){return this.workerChoiceStrategy.choose()}}class T{constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,this.workers=[],this.workersTasksUsage=new Map,this.promiseMap=new Map,this.nextMessageId=0,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();this.opts.enableEvents&&(this.emitter=new n),this.workerChoiceStrategyContext=new m(this,(()=>{const e=this.createAndSetupWorker();return this.registerWorkerMessageListener(e,(r=>{var t;t=h.HARD,(r.kill===t||0===this.getWorkerRunningTasks(e))&&this.destroyWorker(e)})),e}),this.opts.workerChoiceStrategy)}checkFilePath(e){if(!e)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!1===Number.isSafeInteger(r))throw new Error("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new Error("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??k.ROUND_ROBIN,this.opts.enableEvents=e.enableEvents??!0}get numberOfRunningTasks(){return this.promiseMap.size}getWorkerIndex(e){return this.workers.indexOf(e)}getWorkerRunningTasks(e){return this.workersTasksUsage.get(e)?.running}getWorkerAverageTasksRunTime(e){return this.workersTasksUsage.get(e)?.avgRunTime}setWorkerChoiceStrategy(e){this.opts.workerChoiceStrategy=e;for(const e of this.workers)this.resetWorkerTasksUsage(e);this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalGetBusyStatus(){return this.numberOfRunningTasks>=this.numberOfWorkers&&!1===this.findFreeWorker()}findFreeWorker(){for(const e of this.workers)if(0===this.getWorkerRunningTasks(e))return e;return!1}execute(e){const r=this.chooseWorker(),t=++this.nextMessageId,s=this.internalExecute(r,t);return this.checkAndEmitBusy(),e=e??{},this.sendToWorker(r,{data:e,id:t}),s}async destroy(){await Promise.all(this.workers.map((e=>this.destroyWorker(e))))}setupHook(){}beforePromiseWorkerResponseHook(e){this.increaseWorkerRunningTasks(e)}afterPromiseWorkerResponseHook(e,r){this.decreaseWorkerRunningTasks(r.worker),this.stepWorkerRunTasks(r.worker,1),this.updateWorkerTasksRunTime(r.worker,e.taskRunTime)}removeWorker(e){this.workers.splice(this.getWorkerIndex(e),1),this.removeWorkerTasksUsage(e)}chooseWorker(){return this.workerChoiceStrategyContext.execute()}internalExecute(e,r){return this.beforePromiseWorkerResponseHook(e),new Promise(((t,s)=>{this.promiseMap.set(r,{resolve:t,reject:s,worker:e})}))}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??a),e.on("error",this.opts.errorHandler??a),e.on("online",this.opts.onlineHandler??a),e.on("exit",this.opts.exitHandler??a),e.once("exit",(()=>this.removeWorker(e))),this.workers.push(e),this.initWorkerTasksUsage(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(void 0!==e.id){const r=this.promiseMap.get(e.id);void 0!==r&&(this.afterPromiseWorkerResponseHook(e,r),e.error?r.reject(e.error):r.resolve(e.data),this.promiseMap.delete(e.id))}}}checkAndEmitBusy(){this.opts.enableEvents&&this.busy&&this.emitter?.emit("busy")}increaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,1)}decreaseWorkerRunningTasks(e){this.stepWorkerRunningTasks(e,-1)}stepWorkerRunningTasks(e,r){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.running=t.running+r,this.workersTasksUsage.set(e,t)}stepWorkerRunTasks(e,r){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.run=t.run+r,this.workersTasksUsage.set(e,t)}updateWorkerTasksRunTime(e,r){if(!0===this.workerChoiceStrategyContext.getWorkerChoiceStrategy().requiredStatistics.runTime){const t=this.workersTasksUsage.get(e);if(void 0===t)throw new Error("Worker could not be found in worker tasks usage map");t.runTime+=r??0,0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workersTasksUsage.set(e,t)}}initWorkerTasksUsage(e){this.workersTasksUsage.set(e,{run:0,running:0,runTime:0,avgRunTime:0})}removeWorkerTasksUsage(e){this.workersTasksUsage.delete(e)}resetWorkerTasksUsage(e){this.removeWorkerTasksUsage(e),this.initWorkerTasksUsage(e)}}class w extends T{constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){t.setupPrimary({exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}class y extends T{constructor(e,r,t={}){super(e,r,t)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new o.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get busy(){return this.internalGetBusyStatus()}}const R=h.SOFT;class f extends i.AsyncResource{constructor(e,r,t,s,o={killBehavior:R,maxInactiveTime:6e4}){super(e),this.mainWorker=s,this.opts=o,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.lastTaskTimestamp=Date.now(),!1===r&&(this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??6e4)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){void 0!==e.data&&void 0!==e.id?this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):void 0!==e.parent?this.mainWorker=e.parent:void 0!==e.kill&&(this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??R,this.opts.maxInactiveTime=e.maxInactiveTime??6e4,this.opts.async=!!e.async}checkFunctionInput(e){if(!e)throw new Error("fn parameter is mandatory")}getMainWorker(){if(!this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){Date.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??6e4)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=Date.now(),s=e(r.data),o=Date.now()-t;this.sendToMainWorker({data:s,id:r.id,taskRunTime:o})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{this.lastTaskTimestamp=Date.now()}}runAsync(e,r){const t=Date.now();e(r.data).then((e=>{const s=Date.now()-t;return this.sendToMainWorker({data:e,id:r.id,taskRunTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{this.lastTaskTimestamp=Date.now()})).catch(a)}}exports.AbstractWorker=f,exports.ClusterWorker=class extends f{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends w{constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.DynamicThreadPool=class extends y{constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get busy(){return this.workers.length===this.max}},exports.FixedClusterPool=w,exports.FixedThreadPool=y,exports.KillBehaviors=h,exports.ThreadWorker=class extends f{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=k;
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poolifier",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"prepare": "node prepare.js",
|
|
7
8
|
"build": "rollup --config --environment BUILD:development",
|
|
8
9
|
"build:typedoc": "rollup --config --environment BUILD:development --environment DOCUMENTATION",
|
|
9
10
|
"build:prod": "rollup --config",
|
|
@@ -12,12 +13,12 @@
|
|
|
12
13
|
"benchmark:prod": "npm run build:prod && node -r source-map-support/register benchmarks/internal/bench.js",
|
|
13
14
|
"test": "npm run build && nyc mocha 'tests/**/*.test.js'",
|
|
14
15
|
"test:debug": "npm run build && mocha --no-parallel --inspect 'tests/**/*.test.js'",
|
|
15
|
-
"test:prod": "npm run build:prod && nyc mocha 'tests/**/*.test.js'",
|
|
16
16
|
"coverage": "nyc report --reporter=lcov",
|
|
17
17
|
"coverage:html": "nyc report --reporter=html",
|
|
18
18
|
"format": "prettier --loglevel silent --write .; prettierx --write .",
|
|
19
19
|
"lint": "eslint . --cache",
|
|
20
20
|
"lint:fix": "eslint . --cache --fix",
|
|
21
|
+
"lint:report": "eslint . --cache --format json --output-file reports/eslint.json",
|
|
21
22
|
"typedoc": "typedoc",
|
|
22
23
|
"sonar:properties": "./updateSonarProps.sh",
|
|
23
24
|
"prepublishOnly": "npm run build:prod"
|
|
@@ -63,9 +64,9 @@
|
|
|
63
64
|
"lib"
|
|
64
65
|
],
|
|
65
66
|
"devDependencies": {
|
|
66
|
-
"@types/node": "^18.8.
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
68
|
-
"@typescript-eslint/parser": "^5.
|
|
67
|
+
"@types/node": "^18.8.4",
|
|
68
|
+
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
|
69
|
+
"@typescript-eslint/parser": "^5.40.0",
|
|
69
70
|
"benchmark": "^2.1.4",
|
|
70
71
|
"eslint": "^8.25.0",
|
|
71
72
|
"eslint-config-standard": "^17.0.0",
|
|
@@ -76,9 +77,11 @@
|
|
|
76
77
|
"eslint-plugin-n": "^15.3.0",
|
|
77
78
|
"eslint-plugin-node": "^11.1.0",
|
|
78
79
|
"eslint-plugin-prettierx": "^0.18.0",
|
|
79
|
-
"eslint-plugin-promise": "^6.0
|
|
80
|
+
"eslint-plugin-promise": "^6.1.0",
|
|
80
81
|
"eslint-plugin-spellcheck": "^0.0.19",
|
|
81
82
|
"expect": "^29.1.2",
|
|
83
|
+
"husky": "^8.0.1",
|
|
84
|
+
"lint-staged": "^13.0.3",
|
|
82
85
|
"microtime": "^3.1.1",
|
|
83
86
|
"mocha": "^10.0.0",
|
|
84
87
|
"mochawesome": "^7.1.3",
|
|
@@ -86,7 +89,7 @@
|
|
|
86
89
|
"prettier": "^2.7.1",
|
|
87
90
|
"prettier-plugin-organize-imports": "^3.1.1",
|
|
88
91
|
"prettierx": "^0.18.3",
|
|
89
|
-
"rollup": "^
|
|
92
|
+
"rollup": "^3.1.0",
|
|
90
93
|
"rollup-plugin-analyzer": "^4.0.0",
|
|
91
94
|
"rollup-plugin-command": "^1.1.3",
|
|
92
95
|
"rollup-plugin-delete": "^2.0.0",
|
|
@@ -95,7 +98,7 @@
|
|
|
95
98
|
"rollup-plugin-ts": "^3.0.2",
|
|
96
99
|
"sinon": "^14.0.1",
|
|
97
100
|
"source-map-support": "^0.5.21",
|
|
98
|
-
"typedoc": "^0.23.
|
|
101
|
+
"typedoc": "^0.23.16",
|
|
99
102
|
"typescript": "^4.8.4"
|
|
100
103
|
},
|
|
101
104
|
"engines": {
|