poolifier 2.5.2 → 2.5.4
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 +36 -20
- package/lib/index.d.ts +9 -10
- package/lib/index.js +1 -1
- package/lib/index.mjs +1 -1
- package/lib/pools/abstract-pool.d.ts +17 -7
- package/lib/pools/cluster/dynamic.d.ts +2 -4
- package/lib/pools/cluster/fixed.d.ts +6 -6
- package/lib/pools/pool.d.ts +12 -6
- package/lib/pools/selection-strategies/abstract-worker-choice-strategy.d.ts +3 -3
- package/lib/pools/selection-strategies/fair-share-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/least-busy-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/selection-strategies-types.d.ts +7 -3
- package/lib/pools/selection-strategies/weighted-round-robin-worker-choice-strategy.d.ts +2 -2
- package/lib/pools/selection-strategies/worker-choice-strategy-context.d.ts +4 -4
- package/lib/pools/thread/dynamic.d.ts +5 -7
- package/lib/pools/thread/fixed.d.ts +18 -6
- package/lib/pools/worker.d.ts +9 -3
- package/lib/utility-types.d.ts +18 -33
- package/lib/worker/abstract-worker.d.ts +20 -2
- package/lib/worker/cluster-worker.d.ts +3 -2
- package/lib/worker/thread-worker.d.ts +3 -2
- package/lib/worker/worker-functions.d.ts +33 -0
- package/lib/worker/worker-options.d.ts +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -43,6 +43,7 @@ Please consult our [general guidelines](#general-guidance).
|
|
|
43
43
|
- Proper async integration with node async hooks :white_check_mark:
|
|
44
44
|
- Support for worker threads and cluster node modules :white_check_mark:
|
|
45
45
|
- Support sync and async tasks :white_check_mark:
|
|
46
|
+
- Tasks distribution strategies :white_check_mark:
|
|
46
47
|
- General guidance on pools to use :white_check_mark:
|
|
47
48
|
- Widely tested :white_check_mark:
|
|
48
49
|
- Error handling out of the box :white_check_mark:
|
|
@@ -149,11 +150,9 @@ Node versions >= 16.14.x are supported.
|
|
|
149
150
|
|
|
150
151
|
## [API](https://poolifier.github.io/poolifier/)
|
|
151
152
|
|
|
152
|
-
###
|
|
153
|
+
### Pool options
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
`filePath` (mandatory) Path to a file with a worker implementation
|
|
156
|
-
`opts` (optional) An object with these properties:
|
|
155
|
+
An object with these properties:
|
|
157
156
|
|
|
158
157
|
- `messageHandler` (optional) - A function that will listen for message event on each worker
|
|
159
158
|
- `errorHandler` (optional) - A function that will listen for error event on each worker
|
|
@@ -161,14 +160,14 @@ Node versions >= 16.14.x are supported.
|
|
|
161
160
|
- `exitHandler` (optional) - A function that will listen for exit event on each worker
|
|
162
161
|
- `workerChoiceStrategy` (optional) - The worker choice strategy to use in this pool:
|
|
163
162
|
|
|
164
|
-
- `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in a round
|
|
165
|
-
- `WorkerChoiceStrategies.LEAST_USED`: Submit tasks to the
|
|
166
|
-
- `WorkerChoiceStrategies.LEAST_BUSY`: Submit tasks to the
|
|
167
|
-
- `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`: Submit tasks to worker using a weighted round robin scheduling algorithm based on tasks execution time
|
|
168
|
-
- `WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN`: Submit tasks to worker using an interleaved weighted round robin scheduling algorithm based on tasks execution time (experimental)
|
|
169
|
-
- `WorkerChoiceStrategies.FAIR_SHARE`: Submit tasks to worker using a fair share tasks scheduling algorithm based on tasks execution time
|
|
163
|
+
- `WorkerChoiceStrategies.ROUND_ROBIN`: Submit tasks to worker in a round robin fashion
|
|
164
|
+
- `WorkerChoiceStrategies.LEAST_USED`: Submit tasks to the worker with the minimum number of running and ran tasks
|
|
165
|
+
- `WorkerChoiceStrategies.LEAST_BUSY`: Submit tasks to the worker with the minimum tasks total execution time
|
|
166
|
+
- `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`: Submit tasks to worker by using a weighted round robin scheduling algorithm based on tasks execution time
|
|
167
|
+
- `WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN`: Submit tasks to worker by using an interleaved weighted round robin scheduling algorithm based on tasks execution time (experimental)
|
|
168
|
+
- `WorkerChoiceStrategies.FAIR_SHARE`: Submit tasks to worker by using a fair share tasks scheduling algorithm based on tasks execution time
|
|
170
169
|
|
|
171
|
-
`WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks.
|
|
170
|
+
`WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`, `WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks.
|
|
172
171
|
Default: `WorkerChoiceStrategies.ROUND_ROBIN`
|
|
173
172
|
|
|
174
173
|
- `workerChoiceStrategyOptions` (optional) - The worker choice strategy options object to use in this pool.
|
|
@@ -180,11 +179,11 @@ Node versions >= 16.14.x are supported.
|
|
|
180
179
|
Default: `{ medRunTime: false }`
|
|
181
180
|
|
|
182
181
|
- `restartWorkerOnError` (optional) - Restart worker on uncaught error in this pool.
|
|
183
|
-
Default: true
|
|
182
|
+
Default: `true`
|
|
184
183
|
- `enableEvents` (optional) - Events emission enablement in this pool.
|
|
185
|
-
Default: true
|
|
184
|
+
Default: `true`
|
|
186
185
|
- `enableTasksQueue` (optional) - Tasks queue per worker enablement in this pool.
|
|
187
|
-
Default: false
|
|
186
|
+
Default: `false`
|
|
188
187
|
|
|
189
188
|
- `tasksQueueOptions` (optional) - The worker tasks queue options object to use in this pool.
|
|
190
189
|
Properties:
|
|
@@ -193,16 +192,33 @@ Node versions >= 16.14.x are supported.
|
|
|
193
192
|
|
|
194
193
|
Default: `{ concurrency: 1 }`
|
|
195
194
|
|
|
195
|
+
#### Thread pool specific options
|
|
196
|
+
|
|
197
|
+
- `workerOptions` (optional) - An object with the worker options. See [worker_threads](https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options) for more details.
|
|
198
|
+
|
|
199
|
+
#### Cluster pool specific options
|
|
200
|
+
|
|
201
|
+
- `env` (optional) - An object with the environment variables to pass to the worker. See [cluster](https://nodejs.org/api/cluster.html#cluster_cluster_fork_env) for more details.
|
|
202
|
+
|
|
203
|
+
- `settings` (optional) - An object with the cluster settings. See [cluster](https://nodejs.org/api/cluster.html#cluster_cluster_settings) for more details.
|
|
204
|
+
|
|
205
|
+
### `pool = new FixedThreadPool/FixedClusterPool(numberOfThreads/numberOfWorkers, filePath, opts)`
|
|
206
|
+
|
|
207
|
+
`numberOfThreads/numberOfWorkers` (mandatory) Number of workers for this pool
|
|
208
|
+
`filePath` (mandatory) Path to a file with a worker implementation
|
|
209
|
+
`opts` (optional) An object with the pool options properties described above
|
|
210
|
+
|
|
196
211
|
### `pool = new DynamicThreadPool/DynamicClusterPool(min, max, filePath, opts)`
|
|
197
212
|
|
|
198
213
|
`min` (mandatory) Same as FixedThreadPool/FixedClusterPool numberOfThreads/numberOfWorkers, this number of workers will be always active
|
|
199
214
|
`max` (mandatory) Max number of workers that this pool can contain, the new created workers will die after a threshold (default is 1 minute, you can override it in your worker implementation).
|
|
200
|
-
`filePath` (mandatory)
|
|
201
|
-
`opts` (optional)
|
|
215
|
+
`filePath` (mandatory) Path to a file with a worker implementation
|
|
216
|
+
`opts` (optional) An object with the pool options properties described above
|
|
202
217
|
|
|
203
|
-
### `pool.execute(data)`
|
|
218
|
+
### `pool.execute(data, name)`
|
|
204
219
|
|
|
205
220
|
`data` (optional) An object that you want to pass to your worker implementation
|
|
221
|
+
`name` (optional) A string with the task function name that you want to execute on the worker. Default: `'default'`
|
|
206
222
|
This method is available on both pool implementations and returns a promise.
|
|
207
223
|
|
|
208
224
|
### `pool.destroy()`
|
|
@@ -212,14 +228,14 @@ This method will call the terminate method on each worker.
|
|
|
212
228
|
|
|
213
229
|
### `class YourWorker extends ThreadWorker/ClusterWorker`
|
|
214
230
|
|
|
215
|
-
`taskFunctions` (mandatory) The task function
|
|
231
|
+
`taskFunctions` (mandatory) The task function or task functions object that you want to execute on the worker
|
|
216
232
|
`opts` (optional) An object with these properties:
|
|
217
233
|
|
|
218
234
|
- `maxInactiveTime` (optional) - Max time to wait tasks to work on in milliseconds, after this period the new worker will die.
|
|
219
235
|
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.
|
|
220
236
|
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.
|
|
221
237
|
If `killBehavior` is set to `KillBehaviors.SOFT` your tasks have no timeout and your workers will not be terminated until your task is completed.
|
|
222
|
-
Default: 60000
|
|
238
|
+
Default: `60000`
|
|
223
239
|
|
|
224
240
|
- `killBehavior` (optional) - Dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
|
|
225
241
|
**KillBehaviors.SOFT**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted.
|
|
@@ -267,7 +283,7 @@ But in general, **always profile your application**.
|
|
|
267
283
|
|
|
268
284
|
## Contribute
|
|
269
285
|
|
|
270
|
-
Choose your task here [2.
|
|
286
|
+
Choose your task here [2.5.x](https://github.com/orgs/poolifier/projects/1), propose an idea, a fix, an improvement.
|
|
271
287
|
|
|
272
288
|
See [CONTRIBUTING](CONTRIBUTING.md) guidelines.
|
|
273
289
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
export { DynamicClusterPool } from './pools/cluster/dynamic';
|
|
2
|
-
export { FixedClusterPool } from './pools/cluster/fixed';
|
|
3
|
-
export type { ClusterPoolOptions } from './pools/cluster/fixed';
|
|
4
1
|
export type { AbstractPool } from './pools/abstract-pool';
|
|
5
|
-
export {
|
|
6
|
-
export
|
|
2
|
+
export { DynamicClusterPool } from './pools/cluster/dynamic';
|
|
3
|
+
export { FixedClusterPool, type ClusterPoolOptions } from './pools/cluster/fixed';
|
|
4
|
+
export { PoolEvents, PoolTypes, WorkerTypes } from './pools/pool';
|
|
5
|
+
export type { IPool, PoolEmitter, PoolEvent, PoolInfo, PoolOptions, PoolType, TasksQueueOptions, WorkerType } from './pools/pool';
|
|
7
6
|
export type { ErrorHandler, ExitHandler, IWorker, MessageHandler, OnlineHandler, Task, TasksUsage, WorkerNode } from './pools/worker';
|
|
8
7
|
export { WorkerChoiceStrategies } from './pools/selection-strategies/selection-strategies-types';
|
|
9
|
-
export type { IWorkerChoiceStrategy,
|
|
8
|
+
export type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types';
|
|
10
9
|
export type { WorkerChoiceStrategyContext } from './pools/selection-strategies/worker-choice-strategy-context';
|
|
11
10
|
export { DynamicThreadPool } from './pools/thread/dynamic';
|
|
12
|
-
export { FixedThreadPool } from './pools/thread/fixed';
|
|
13
|
-
export type {
|
|
14
|
-
export type { AbstractWorker } from './worker/abstract-worker';
|
|
11
|
+
export { FixedThreadPool, type ThreadPoolOptions, type ThreadWorkerWithMessageChannel } from './pools/thread/fixed';
|
|
12
|
+
export type { AbstractWorker, TaskPerformance } from './worker/abstract-worker';
|
|
15
13
|
export { ClusterWorker } from './worker/cluster-worker';
|
|
16
14
|
export { ThreadWorker } from './worker/thread-worker';
|
|
17
15
|
export { KillBehaviors } from './worker/worker-options';
|
|
18
16
|
export type { KillBehavior, WorkerOptions } from './worker/worker-options';
|
|
19
|
-
export type {
|
|
17
|
+
export type { TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './worker/worker-functions';
|
|
18
|
+
export type { Draft, MessageValue, PromiseResponseWrapper, WorkerStatistics } from './utility-types';
|
|
20
19
|
export type { CircularArray } from './circular-array';
|
|
21
20
|
export type { Queue } from './queue';
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("node:events"),t=require("node:cluster"),r=require("node:crypto"),s=require("node:os"),i=require("node:worker_threads"),o=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"});class a extends e{}const h=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),u=Object.freeze((()=>{})),k={medRunTime:!1,medWaitTime:!1},d=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},c=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class m extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class p{items;head;tail;max;constructor(){this.items={},this.head=0,this.tail=0,this.max=0}get size(){return this.tail-this.head}get maxSize(){return this.max}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size>this.max&&(this.max=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}peek(){if(!(this.size<=0))return this.items[this.head]}}const g=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class T{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=k){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setRequiredStatistics(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!1,this.requiredStatistics.medWaitTime=e.medWaitTime),this.requiredStatistics.medWaitTime&&!1===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!0,this.requiredStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??k,this.setRequiredStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.requiredStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of s.cpus()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s.cpus().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class w extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[r]&&this.computeWorkerVirtualTaskEndTimestamp(r);const s=this.workersVirtualTaskEndTimestamp[r];s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class W extends T{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let r=this.currentRoundId;r<this.roundWeights.length;r++)for(let s=this.currentWorkerNodeId;s<this.pool.workerNodes.length;s++){if((this.opts.weights?.[s]??this.defaultWorkerWeight)>=this.roundWeights[r]){e=r,t=s;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const r=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,r}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class f extends T{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class y extends T{constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class N extends T{nextWorkerNodeId=0;constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class S extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=g.ROUND_ROBIN,r=k){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new(N.bind(this))(e,r)],[g.LEAST_USED,new(y.bind(this))(e,r)],[g.LEAST_BUSY,new(f.bind(this))(e,r)],[g.FAIR_SHARE,new(w.bind(this))(e,r)],[g.WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,r)],[g.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(W.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class x{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!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.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new a),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)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 TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!c(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??g.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??k,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!c(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!c(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.tasksUsage.running?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.tasksUsage.running>0?e+1:e),0),runningTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new m,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new m,avgWaitTime:0,medWaitTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const s=performance.now(),i=this.chooseWorkerNode(),o={name:t,data:e??{},submissionTimestamp:s,id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:this.workerNodes[i].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[i].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(i,o):this.executeTask(i,o),this.workerChoiceStrategyContext.update(i),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--r.running,++r.run,null!=t.error&&++r.error,this.updateRunTimeTasksUsage(r,t),this.updateWaitTimeTasksUsage(r,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==e.run&&(e.avgRunTime=e.runTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=d(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgWaitTime&&0!==e.run&&(e.avgWaitTime=e.waitTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=d(e.waitTimeHistory)))}chooseWorkerNode(){let e;if(this.type===n.dynamic&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const r=this.getWorkerNodeKey(t);var s;s=l.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].tasksUsage.running)&&(this.flushTasksQueue(r),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return e}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(h.error,e)})),!0===this.opts.restartWorkerOnError&&e.on("error",(()=>{this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?(t.reject(e.error),null!=this.emitter&&this.emitter.emit(h.taskError,{error:e.error,errorData:e.errorData})):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(h.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(h.full,this.info))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new m,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new m,avgWaitTime:0,medWaitTime:0,error:0},tasksQueue:new p})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class E extends x{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return n.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class I extends x{constructor(e,t,r={}){super(e,t,r)}isMain(){return i.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new i.Worker(this.filePath,{env:i.SHARE_ENV})}afterWorkerSetup(e){const{port1:t,port2:r}=new i.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return n.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}const b="default",v=6e4,O=l.SOFT;class C extends o.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:O,maxInactiveTime:v}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??v)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??O,this.opts.maxInactiveTime=e.maxInactiveTime??v,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(b,e.bind(this));else{if(!c(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[r,s]of Object.entries(e)){if("function"!=typeof s)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(r,s.bind(this)),t&&(this.taskFunctions.set(b,s.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??v)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{const r=performance.now(),s=r-(t.submissionTimestamp??0),i=e(t.data),o=performance.now()-r;this.sendToMainWorker({data:i,runTime:o,waitTime:s,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now(),s=r-(t.submissionTimestamp??0);e(t.data).then((e=>{const i=performance.now()-r;return this.sendToMainWorker({data:e,runTime:i,waitTime:s,id:t.id}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(u)}getTaskFunction(e){e=e??b;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}}exports.ClusterWorker=class extends C{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 E{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get full(){return this.workerNodes.length>=this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends I{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return n.dynamic}get full(){return this.workerNodes.length>=this.max}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=E,exports.FixedThreadPool=I,exports.KillBehaviors=l,exports.PoolEvents=h,exports.PoolTypes=n,exports.ThreadWorker=class extends C{constructor(e,t={}){super("worker-thread-pool:poolifier",i.isMainThread,e,i.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=g;
|
|
1
|
+
"use strict";var e=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),r=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");const a=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),h=Object.freeze({cluster:"cluster",thread:"thread"});class u extends e{}const k=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),c=Object.freeze((()=>{})),d={medRunTime:!1,medWaitTime:!1},l=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},m=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),p=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class T extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class g{items;head;tail;max;constructor(){this.items={},this.head=0,this.tail=0,this.max=0}get size(){return this.tail-this.head}get maxSize(){return this.max}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size>this.max&&(this.max=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}peek(){if(!(this.size<=0))return this.items[this.head]}}const w=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class W{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;taskStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};constructor(e,t=d){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatistics(e){this.taskStatistics.avgRunTime&&!0===e.medRunTime&&(this.taskStatistics.avgRunTime=!1,this.taskStatistics.medRunTime=e.medRunTime),this.taskStatistics.medRunTime&&!1===e.medRunTime&&(this.taskStatistics.avgRunTime=!0,this.taskStatistics.medRunTime=e.medRunTime),this.taskStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.taskStatistics.avgWaitTime=!1,this.taskStatistics.medWaitTime=e.medWaitTime),this.taskStatistics.medWaitTime&&!1===e.medWaitTime&&(this.taskStatistics.avgWaitTime=!0,this.taskStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??d,this.setTaskStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.taskStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.taskStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i.cpus().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class f extends W{taskStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=d){super(e,t),this.setTaskStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[s]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[s]&&this.computeWorkerVirtualTaskEndTimestamp(s);const r=this.workersVirtualTaskEndTimestamp[s];r<t&&(t=r,e=s)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class y extends W{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=d){super(e,t),this.setTaskStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.currentRoundId;s<this.roundWeights.length;s++)for(let r=this.currentWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const s=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,s}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class S extends W{taskStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};constructor(e,t=d){super(e,t),this.setTaskStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){let e,t=1/0;for(const[s,r]of this.pool.workerNodes.entries()){const i=r.tasksUsage.runTime;if(0===i)return s;i<t&&(t=i,e=s)}return e}remove(){return!0}}class N extends W{constructor(e,t=d){super(e,t),this.setTaskStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,s=1/0;for(const[e,r]of this.pool.workerNodes.entries()){const i=r.tasksUsage,o=i.ran+i.running;if(0===o)return e;o<s&&(s=o,t=e)}return t}remove(){return!0}}class x extends W{nextWorkerNodeId=0;constructor(e,t=d){super(e,t),this.setTaskStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class E extends W{taskStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=d){super(e,t),this.setTaskStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=w.ROUND_ROBIN,s=d){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[w.ROUND_ROBIN,new(x.bind(this))(e,s)],[w.LEAST_USED,new(N.bind(this))(e,s)],[w.LEAST_BUSY,new(S.bind(this))(e,s)],[w.FAIR_SHARE,new(f.bind(this))(e,s)],[w.WEIGHTED_ROUND_ROBIN,new(E.bind(this))(e,s)],[w.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(y.bind(this))(e,s)]])}getTaskStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class v{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!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.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)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 TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===a.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!m(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??w.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??d,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(w).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!m(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!m(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.tasksUsage.running?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.tasksUsage.running>0?e+1:e),0),runningTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{ran:0,running:0,runTime:0,runTimeHistory:new T,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new T,avgWaitTime:0,medWaitTime:0,error:0,elu:void 0}),this.setWorkerStatistics(e.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const i=r.performance.now(),o=this.chooseWorkerNode(),n={name:t,data:e??{},timestamp:i,id:s.randomUUID()},a=new Promise(((e,t)=>{this.promiseResponseMap.set(n.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,n):this.executeTask(o,n),this.workerChoiceStrategyContext.update(o),this.checkAndEmitEvents(),a}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--s.running,++s.ran,null!=t.error&&++s.error,this.updateRunTimeTasksUsage(s,t),this.updateWaitTimeTasksUsage(s,t),this.updateEluTasksUsage(s,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getTaskStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getTaskStatistics().avgRunTime&&0!==e.ran&&(e.avgRunTime=e.runTime/e.ran),this.workerChoiceStrategyContext.getTaskStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=l(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getTaskStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getTaskStatistics().avgWaitTime&&0!==e.ran&&(e.avgWaitTime=e.waitTime/e.ran),this.workerChoiceStrategyContext.getTaskStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=l(e.waitTimeHistory)))}updateEluTasksUsage(e,t){this.workerChoiceStrategyContext.getTaskStatistics().elu&&(null!=e.elu&&null!=t.elu?e.elu={idle:e.elu.idle+t.elu.idle,active:e.elu.active+t.elu.active,utilization:(e.elu.utilization+t.elu.utilization)/2}:null!=t.elu&&(e.elu=t.elu))}chooseWorkerNode(){let e;if(this.type===a.dynamic&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const s=this.getWorkerNodeKey(t);var r;r=p.HARD,(e.kill===r||null!=e.kill&&0===this.workerNodes[s].tasksUsage.running)&&(this.flushTasksQueue(s),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return e}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??c),e.on("error",this.opts.errorHandler??c),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(k.error,e)})),e.on("error",(()=>{!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??c),e.on("exit",this.opts.exitHandler??c),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?(t.reject(e.error),null!=this.emitter&&this.emitter.emit(k.taskError,{error:e.error,errorData:e.errorData})):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(k.busy,this.info),this.type===a.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{ran:0,running:0,runTime:0,runTimeHistory:new T,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new T,avgWaitTime:0,medWaitTime:0,error:0,elu:void 0},tasksQueue:new g})}setWorkerNode(e,t,s,r){this.workerNodes[e]={worker:t,tasksUsage:s,tasksQueue:r}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatistics().runTime,waitTime:this.workerChoiceStrategyContext.getTaskStatistics().waitTime,elu:this.workerChoiceStrategyContext.getTaskStatistics().elu}})}}class I extends v{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return a.fixed}get worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class b extends v{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV,...this.opts.workerOptions})}afterWorkerSetup(e){const{port1:t,port2:s}=new o.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return a.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const O="default",C=6e4,z=p.SOFT;class U extends n.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:z,maxInactiveTime:C}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??C)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??z,this.opts.maxInactiveTime=e.maxInactiveTime??C,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(O,e.bind(this));else{if(!m(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(O,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill?(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy()):null!=e.statistics&&(this.statistics=e.statistics)}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){r.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??C)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{const s=this.beginTaskPerformance(t),r=e(t.data),{runTime:i,waitTime:o,elu:n}=this.endTaskPerformance(s);this.sendToMainWorker({data:r,runTime:i,waitTime:o,elu:n,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({error:s,errorData:t.data,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())}}runAsync(e,t){const s=this.beginTaskPerformance(t);e(t.data).then((e=>{const{runTime:r,waitTime:i,elu:o}=this.endTaskPerformance(s);return this.sendToMainWorker({data:e,runTime:r,waitTime:i,elu:o,id:t.id}),null})).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({error:s,errorData:t.data,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.performance.now())})).catch(c)}getTaskFunction(e){e=e??O;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(e){const t=r.performance.now();return{timestamp:t,...this.statistics.waitTime&&{waitTime:t-(e.timestamp??t)},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization()}}}endTaskPerformance(e){return{...e,...this.statistics.runTime&&{runTime:r.performance.now()-e.timestamp},...this.statistics.elu&&{elu:r.performance.eventLoopUtilization(e.elu)}}}}exports.ClusterWorker=class extends U{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends I{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return a.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends b{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return a.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=I,exports.FixedThreadPool=b,exports.KillBehaviors=p,exports.PoolEvents=k,exports.PoolTypes=a,exports.ThreadWorker=class extends U{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=w,exports.WorkerTypes=h;
|
package/lib/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"node:events";import t from"node:cluster";import r from"node:crypto";import{cpus as s}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as a,parentPort as h}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";const k=Object.freeze({fixed:"fixed",dynamic:"dynamic"});class d extends e{}const c=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),l=Object.freeze((()=>{})),m={medRunTime:!1,medWaitTime:!1},p=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),T=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class w extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class W{items;head;tail;max;constructor(){this.items={},this.head=0,this.tail=0,this.max=0}get size(){return this.tail-this.head}get maxSize(){return this.max}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size>this.max&&(this.max=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}peek(){if(!(this.size<=0))return this.items[this.head]}}const f=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class y{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=m){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setRequiredStatistics(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!1,this.requiredStatistics.medWaitTime=e.medWaitTime),this.requiredStatistics.medWaitTime&&!1===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!0,this.requiredStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??m,this.setRequiredStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.requiredStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of s()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class N extends y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[r]&&this.computeWorkerVirtualTaskEndTimestamp(r);const s=this.workersVirtualTaskEndTimestamp[r];s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends y{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let r=this.currentRoundId;r<this.roundWeights.length;r++)for(let s=this.currentWorkerNodeId;s<this.pool.workerNodes.length;s++){if((this.opts.weights?.[s]??this.defaultWorkerWeight)>=this.roundWeights[r]){e=r,t=s;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const r=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,r}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class R extends y{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class x extends y{constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class I extends y{nextWorkerNodeId=0;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class E extends y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class b{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=f.ROUND_ROBIN,r=m){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[f.ROUND_ROBIN,new(I.bind(this))(e,r)],[f.LEAST_USED,new(x.bind(this))(e,r)],[f.LEAST_BUSY,new(R.bind(this))(e,r)],[f.FAIR_SHARE,new(N.bind(this))(e,r)],[f.WEIGHTED_ROUND_ROBIN,new(E.bind(this))(e,r)],[f.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class O{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!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.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new d),this.workerChoiceStrategyContext=new b(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)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 TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??f.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??m,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(f).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.tasksUsage.running?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.tasksUsage.running>0?e+1:e),0),runningTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new w,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new w,avgWaitTime:0,medWaitTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const s=performance.now(),i=this.chooseWorkerNode(),o={name:t,data:e??{},submissionTimestamp:s,id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:this.workerNodes[i].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[i].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(i,o):this.executeTask(i,o),this.workerChoiceStrategyContext.update(i),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--r.running,++r.run,null!=t.error&&++r.error,this.updateRunTimeTasksUsage(r,t),this.updateWaitTimeTasksUsage(r,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==e.run&&(e.avgRunTime=e.runTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=p(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgWaitTime&&0!==e.run&&(e.avgWaitTime=e.waitTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=p(e.waitTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.dynamic&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const r=this.getWorkerNodeKey(t);var s;s=T.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].tasksUsage.running)&&(this.flushTasksQueue(r),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return e}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??l),e.on("error",this.opts.errorHandler??l),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(c.error,e)})),!0===this.opts.restartWorkerOnError&&e.on("error",(()=>{this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??l),e.on("exit",this.opts.exitHandler??l),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?(t.reject(e.error),null!=this.emitter&&this.emitter.emit(c.taskError,{error:e.error,errorData:e.errorData})):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(c.busy,this.info),this.type===k.dynamic&&this.full&&this.emitter?.emit(c.full,this.info))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new w,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new w,avgWaitTime:0,medWaitTime:0,error:0},tasksQueue:new W})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class v extends O{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class C extends v{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.dynamic}get maxSize(){return this.max}get full(){return this.workerNodes.length>=this.max}get busy(){return this.full&&this.internalBusy()}}class z extends O{constructor(e,t,r={}){super(e,t,r)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:r}=new a;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class q extends z{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.dynamic}get full(){return this.workerNodes.length>=this.max}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}const U="default",Q=6e4,A=T.SOFT;class F extends u{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:A,maxInactiveTime:Q}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??Q)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??A,this.opts.maxInactiveTime=e.maxInactiveTime??Q,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(U,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[r,s]of Object.entries(e)){if("function"!=typeof s)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(r,s.bind(this)),t&&(this.taskFunctions.set(U,s.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??Q)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{const r=performance.now(),s=r-(t.submissionTimestamp??0),i=e(t.data),o=performance.now()-r;this.sendToMainWorker({data:i,runTime:o,waitTime:s,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now(),s=r-(t.submissionTimestamp??0);e(t.data).then((e=>{const i=performance.now()-r;return this.sendToMainWorker({data:e,runTime:i,waitTime:s,id:t.id}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(l)}getTaskFunction(e){e=e??U;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}}class M 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}}class D extends F{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,h,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{M as ClusterWorker,C as DynamicClusterPool,q as DynamicThreadPool,v as FixedClusterPool,z as FixedThreadPool,T as KillBehaviors,c as PoolEvents,k as PoolTypes,D as ThreadWorker,f as WorkerChoiceStrategies};
|
|
1
|
+
import e from"node:events";import t from"node:cluster";import s from"node:crypto";import{performance as r}from"node:perf_hooks";import{cpus as i}from"node:os";import{isMainThread as o,Worker as n,SHARE_ENV as a,MessageChannel as h,parentPort as u}from"node:worker_threads";import{AsyncResource as k}from"node:async_hooks";const c=Object.freeze({fixed:"fixed",dynamic:"dynamic"}),d=Object.freeze({cluster:"cluster",thread:"thread"});class l extends e{}const m=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),p=Object.freeze((()=>{})),g={medRunTime:!1,medWaitTime:!1},T=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},w=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),W=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class f extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class y{items;head;tail;max;constructor(){this.items={},this.head=0,this.tail=0,this.max=0}get size(){return this.tail-this.head}get maxSize(){return this.max}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size>this.max&&(this.max=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}peek(){if(!(this.size<=0))return this.items[this.head]}}const S=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class N{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;taskStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};constructor(e,t=g){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatistics(e){this.taskStatistics.avgRunTime&&!0===e.medRunTime&&(this.taskStatistics.avgRunTime=!1,this.taskStatistics.medRunTime=e.medRunTime),this.taskStatistics.medRunTime&&!1===e.medRunTime&&(this.taskStatistics.avgRunTime=!0,this.taskStatistics.medRunTime=e.medRunTime),this.taskStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.taskStatistics.avgWaitTime=!1,this.taskStatistics.medWaitTime=e.medWaitTime),this.taskStatistics.medWaitTime&&!1===e.medWaitTime&&(this.taskStatistics.avgWaitTime=!0,this.taskStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??g,this.setTaskStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.taskStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.taskStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of i()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/i().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class x extends N{taskStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=g){super(e,t),this.setTaskStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[s]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[s]&&this.computeWorkerVirtualTaskEndTimestamp(s);const r=this.workersVirtualTaskEndTimestamp[s];r<t&&(t=r,e=s)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class E extends N{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=g){super(e,t),this.setTaskStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let s=this.currentRoundId;s<this.roundWeights.length;s++)for(let r=this.currentWorkerNodeId;r<this.pool.workerNodes.length;r++){if((this.opts.weights?.[r]??this.defaultWorkerWeight)>=this.roundWeights[s]){e=s,t=r;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const s=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,s}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class R extends N{taskStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};constructor(e,t=g){super(e,t),this.setTaskStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){let e,t=1/0;for(const[s,r]of this.pool.workerNodes.entries()){const i=r.tasksUsage.runTime;if(0===i)return s;i<t&&(t=i,e=s)}return e}remove(){return!0}}class I extends N{constructor(e,t=g){super(e,t),this.setTaskStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,s=1/0;for(const[e,r]of this.pool.workerNodes.entries()){const i=r.tasksUsage,o=i.ran+i.running;if(0===o)return e;o<s&&(s=o,t=e)}return t}remove(){return!0}}class v extends N{nextWorkerNodeId=0;constructor(e,t=g){super(e,t),this.setTaskStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class b extends N{taskStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1,elu:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=g){super(e,t),this.setTaskStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class O{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=S.ROUND_ROBIN,s=g){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[S.ROUND_ROBIN,new(v.bind(this))(e,s)],[S.LEAST_USED,new(I.bind(this))(e,s)],[S.LEAST_BUSY,new(R.bind(this))(e,s)],[S.FAIR_SHARE,new(x.bind(this))(e,s)],[S.WEIGHTED_ROUND_ROBIN,new(b.bind(this))(e,s)],[S.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(E.bind(this))(e,s)]])}getTaskStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class C{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!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.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),!0===this.opts.enableEvents&&(this.emitter=new l),this.workerChoiceStrategyContext=new O(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker()}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)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 TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===c.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!w(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??S.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??g,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(S).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!w(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!w(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.tasksUsage.running?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.tasksUsage.running>0?e+1:e),0),runningTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e,this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t);for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{ran:0,running:0,runTime:0,runTimeHistory:new f,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new f,avgWaitTime:0,medWaitTime:0,error:0,elu:void 0}),this.setWorkerStatistics(e.worker)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):null!=this.opts.tasksQueueOptions&&delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}get full(){return this.workerNodes.length>=this.maxSize}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const i=r.now(),o=this.chooseWorkerNode(),n={name:t,data:e??{},timestamp:i,id:s.randomUUID()},a=new Promise(((e,t)=>{this.promiseResponseMap.set(n.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,n):this.executeTask(o,n),this.workerChoiceStrategyContext.update(o),this.checkAndEmitEvents(),a}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const s=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--s.running,++s.ran,null!=t.error&&++s.error,this.updateRunTimeTasksUsage(s,t),this.updateWaitTimeTasksUsage(s,t),this.updateEluTasksUsage(s,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getTaskStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getTaskStatistics().avgRunTime&&0!==e.ran&&(e.avgRunTime=e.runTime/e.ran),this.workerChoiceStrategyContext.getTaskStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=T(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getTaskStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getTaskStatistics().avgWaitTime&&0!==e.ran&&(e.avgWaitTime=e.waitTime/e.ran),this.workerChoiceStrategyContext.getTaskStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=T(e.waitTimeHistory)))}updateEluTasksUsage(e,t){this.workerChoiceStrategyContext.getTaskStatistics().elu&&(null!=e.elu&&null!=t.elu?e.elu={idle:e.elu.idle+t.elu.idle,active:e.elu.active+t.elu.active,utilization:(e.elu.utilization+t.elu.utilization)/2}:null!=t.elu&&(e.elu=t.elu))}chooseWorkerNode(){let e;if(this.type===c.dynamic&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const s=this.getWorkerNodeKey(t);var r;r=W.HARD,(e.kill===r||null!=e.kill&&0===this.workerNodes[s].tasksUsage.running)&&(this.flushTasksQueue(s),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return e}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??p),e.on("error",this.opts.errorHandler??p),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(m.error,e)})),e.on("error",(()=>{!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??p),e.on("exit",this.opts.exitHandler??p),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.setWorkerStatistics(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?(t.reject(e.error),null!=this.emitter&&this.emitter.emit(m.taskError,{error:e.error,errorData:e.errorData})):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(m.busy,this.info),this.type===c.dynamic&&this.full&&this.emitter?.emit(m.full,this.info))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{ran:0,running:0,runTime:0,runTimeHistory:new f,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new f,avgWaitTime:0,medWaitTime:0,error:0,elu:void 0},tasksQueue:new y})}setWorkerNode(e,t,s,r){this.workerNodes[e]={worker:t,tasksUsage:s,tasksQueue:r}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}setWorkerStatistics(e){this.sendToWorker(e,{statistics:{runTime:this.workerChoiceStrategyContext.getTaskStatistics().runTime,waitTime:this.workerChoiceStrategyContext.getTaskStatistics().waitTime,elu:this.workerChoiceStrategyContext.getTaskStatistics().elu}})}}class z extends C{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return c.fixed}get worker(){return d.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class U extends z{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return c.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}class Q extends C{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}isMain(){return o}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new n(this.filePath,{env:a,...this.opts.workerOptions})}afterWorkerSetup(e){const{port1:t,port2:s}=new h;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return c.fixed}get worker(){return d.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class A extends Q{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return c.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}const F="default",M=6e4,D=W.SOFT;class V extends k{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,s,i,o={killBehavior:D,maxInactiveTime:M}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(s),this.isMain||(this.lastTaskTimestamp=r.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??M)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??D,this.opts.maxInactiveTime=e.maxInactiveTime??M,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(F,e.bind(this));else{if(!w(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[s,r]of Object.entries(e)){if("function"!=typeof r)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(s,r.bind(this)),t&&(this.taskFunctions.set(F,r.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill?(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy()):null!=e.statistics&&(this.statistics=e.statistics)}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){r.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??M)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{const s=this.beginTaskPerformance(t),r=e(t.data),{runTime:i,waitTime:o,elu:n}=this.endTaskPerformance(s);this.sendToMainWorker({data:r,runTime:i,waitTime:o,elu:n,id:t.id})}catch(e){const s=this.handleError(e);this.sendToMainWorker({error:s,errorData:t.data,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=r.now())}}runAsync(e,t){const s=this.beginTaskPerformance(t);e(t.data).then((e=>{const{runTime:r,waitTime:i,elu:o}=this.endTaskPerformance(s);return this.sendToMainWorker({data:e,runTime:r,waitTime:i,elu:o,id:t.id}),null})).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({error:s,errorData:t.data,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=r.now())})).catch(p)}getTaskFunction(e){e=e??F;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(e){const t=r.now();return{timestamp:t,...this.statistics.waitTime&&{waitTime:t-(e.timestamp??t)},...this.statistics.elu&&{elu:r.eventLoopUtilization()}}}endTaskPerformance(e){return{...e,...this.statistics.runTime&&{runTime:r.now()-e.timestamp},...this.statistics.elu&&{elu:r.eventLoopUtilization(e.elu)}}}}class _ extends V{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class H extends V{constructor(e,t={}){super("worker-thread-pool:poolifier",o,e,u,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{_ as ClusterWorker,U as DynamicClusterPool,A as DynamicThreadPool,z as FixedClusterPool,Q as FixedThreadPool,W as KillBehaviors,m as PoolEvents,c as PoolTypes,H as ThreadWorker,S as WorkerChoiceStrategies,d as WorkerTypes};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
|
|
2
|
-
import { type IPool, PoolEmitter, type PoolInfo, type PoolOptions, type PoolType, type TasksQueueOptions } from './pool';
|
|
2
|
+
import { type IPool, PoolEmitter, type PoolInfo, type PoolOptions, type PoolType, type TasksQueueOptions, type WorkerType } from './pool';
|
|
3
3
|
import type { IWorker, WorkerNode } from './worker';
|
|
4
4
|
import { type WorkerChoiceStrategy, type WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types';
|
|
5
5
|
import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context';
|
|
@@ -11,9 +11,9 @@ import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choic
|
|
|
11
11
|
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
12
12
|
*/
|
|
13
13
|
export declare abstract class AbstractPool<Worker extends IWorker, Data = unknown, Response = unknown> implements IPool<Worker, Data, Response> {
|
|
14
|
-
readonly numberOfWorkers: number;
|
|
15
|
-
readonly filePath: string;
|
|
16
|
-
readonly opts: PoolOptions<Worker>;
|
|
14
|
+
protected readonly numberOfWorkers: number;
|
|
15
|
+
protected readonly filePath: string;
|
|
16
|
+
protected readonly opts: PoolOptions<Worker>;
|
|
17
17
|
/** @inheritDoc */
|
|
18
18
|
readonly workerNodes: Array<WorkerNode<Worker, Data>>;
|
|
19
19
|
/** @inheritDoc */
|
|
@@ -48,9 +48,17 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
48
48
|
private checkValidWorkerChoiceStrategyOptions;
|
|
49
49
|
private checkValidTasksQueueOptions;
|
|
50
50
|
/** @inheritDoc */
|
|
51
|
-
abstract get type(): PoolType;
|
|
52
|
-
/** @inheritDoc */
|
|
53
51
|
get info(): PoolInfo;
|
|
52
|
+
/**
|
|
53
|
+
* Pool type.
|
|
54
|
+
*
|
|
55
|
+
* If it is `'dynamic'`, it provides the `max` property.
|
|
56
|
+
*/
|
|
57
|
+
protected abstract get type(): PoolType;
|
|
58
|
+
/**
|
|
59
|
+
* Gets the worker type.
|
|
60
|
+
*/
|
|
61
|
+
protected abstract get worker(): WorkerType;
|
|
54
62
|
/**
|
|
55
63
|
* Pool minimum size.
|
|
56
64
|
*/
|
|
@@ -80,7 +88,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
80
88
|
*
|
|
81
89
|
* The pool filling boolean status.
|
|
82
90
|
*/
|
|
83
|
-
protected
|
|
91
|
+
protected get full(): boolean;
|
|
84
92
|
/**
|
|
85
93
|
* Whether the pool is busy or not.
|
|
86
94
|
*
|
|
@@ -126,6 +134,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
126
134
|
protected afterTaskExecutionHook(worker: Worker, message: MessageValue<Response>): void;
|
|
127
135
|
private updateRunTimeTasksUsage;
|
|
128
136
|
private updateWaitTimeTasksUsage;
|
|
137
|
+
private updateEluTasksUsage;
|
|
129
138
|
/**
|
|
130
139
|
* Chooses a worker node for the next task.
|
|
131
140
|
*
|
|
@@ -208,4 +217,5 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
|
|
|
208
217
|
private tasksQueueSize;
|
|
209
218
|
private flushTasksQueue;
|
|
210
219
|
private flushTasksQueues;
|
|
220
|
+
private setWorkerStatistics;
|
|
211
221
|
}
|
|
@@ -12,7 +12,7 @@ import { type ClusterPoolOptions, FixedClusterPool } from './fixed';
|
|
|
12
12
|
* @since 2.0.0
|
|
13
13
|
*/
|
|
14
14
|
export declare class DynamicClusterPool<Data = unknown, Response = unknown> extends FixedClusterPool<Data, Response> {
|
|
15
|
-
readonly max: number;
|
|
15
|
+
protected readonly max: number;
|
|
16
16
|
/**
|
|
17
17
|
* Constructs a new poolifier dynamic cluster pool.
|
|
18
18
|
*
|
|
@@ -23,11 +23,9 @@ export declare class DynamicClusterPool<Data = unknown, Response = unknown> exte
|
|
|
23
23
|
*/
|
|
24
24
|
constructor(min: number, max: number, filePath: string, opts?: ClusterPoolOptions);
|
|
25
25
|
/** @inheritDoc */
|
|
26
|
-
get type(): PoolType;
|
|
26
|
+
protected get type(): PoolType;
|
|
27
27
|
/** @inheritDoc */
|
|
28
28
|
protected get maxSize(): number;
|
|
29
29
|
/** @inheritDoc */
|
|
30
|
-
protected get full(): boolean;
|
|
31
|
-
/** @inheritDoc */
|
|
32
30
|
protected get busy(): boolean;
|
|
33
31
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import type
|
|
2
|
+
import { type ClusterSettings, type Worker } from 'node:cluster';
|
|
3
3
|
import type { MessageValue } from '../../utility-types';
|
|
4
4
|
import { AbstractPool } from '../abstract-pool';
|
|
5
|
-
import { type PoolOptions, type PoolType } from '../pool';
|
|
5
|
+
import { type PoolOptions, type PoolType, type WorkerType } from '../pool';
|
|
6
6
|
/**
|
|
7
7
|
* Options for a poolifier cluster pool.
|
|
8
8
|
*/
|
|
@@ -33,7 +33,7 @@ export interface ClusterPoolOptions extends PoolOptions<Worker> {
|
|
|
33
33
|
* @since 2.0.0
|
|
34
34
|
*/
|
|
35
35
|
export declare class FixedClusterPool<Data = unknown, Response = unknown> extends AbstractPool<Worker, Data, Response> {
|
|
36
|
-
readonly opts: ClusterPoolOptions;
|
|
36
|
+
protected readonly opts: ClusterPoolOptions;
|
|
37
37
|
/**
|
|
38
38
|
* Constructs a new poolifier fixed cluster pool.
|
|
39
39
|
*
|
|
@@ -57,13 +57,13 @@ export declare class FixedClusterPool<Data = unknown, Response = unknown> extend
|
|
|
57
57
|
/** @inheritDoc */
|
|
58
58
|
protected afterWorkerSetup(worker: Worker): void;
|
|
59
59
|
/** @inheritDoc */
|
|
60
|
-
get type(): PoolType;
|
|
60
|
+
protected get type(): PoolType;
|
|
61
|
+
/** @inheritDoc */
|
|
62
|
+
protected get worker(): WorkerType;
|
|
61
63
|
/** @inheritDoc */
|
|
62
64
|
protected get minSize(): number;
|
|
63
65
|
/** @inheritDoc */
|
|
64
66
|
protected get maxSize(): number;
|
|
65
67
|
/** @inheritDoc */
|
|
66
|
-
protected get full(): boolean;
|
|
67
|
-
/** @inheritDoc */
|
|
68
68
|
protected get busy(): boolean;
|
|
69
69
|
}
|
package/lib/pools/pool.d.ts
CHANGED
|
@@ -19,6 +19,17 @@ export declare const PoolTypes: Readonly<{
|
|
|
19
19
|
* Pool type.
|
|
20
20
|
*/
|
|
21
21
|
export type PoolType = keyof typeof PoolTypes;
|
|
22
|
+
/**
|
|
23
|
+
* Enumeration of worker types.
|
|
24
|
+
*/
|
|
25
|
+
export declare const WorkerTypes: Readonly<{
|
|
26
|
+
readonly cluster: "cluster";
|
|
27
|
+
readonly thread: "thread";
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Worker type.
|
|
31
|
+
*/
|
|
32
|
+
export type WorkerType = keyof typeof WorkerTypes;
|
|
22
33
|
/**
|
|
23
34
|
* Pool events emitter.
|
|
24
35
|
*/
|
|
@@ -42,6 +53,7 @@ export type PoolEvent = keyof typeof PoolEvents;
|
|
|
42
53
|
*/
|
|
43
54
|
export interface PoolInfo {
|
|
44
55
|
type: PoolType;
|
|
56
|
+
worker: WorkerType;
|
|
45
57
|
minSize: number;
|
|
46
58
|
maxSize: number;
|
|
47
59
|
workerNodes: number;
|
|
@@ -123,12 +135,6 @@ export interface PoolOptions<Worker extends IWorker> {
|
|
|
123
135
|
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
124
136
|
*/
|
|
125
137
|
export interface IPool<Worker extends IWorker, Data = unknown, Response = unknown> {
|
|
126
|
-
/**
|
|
127
|
-
* Pool type.
|
|
128
|
-
*
|
|
129
|
-
* If it is `'dynamic'`, it provides the `max` property.
|
|
130
|
-
*/
|
|
131
|
-
readonly type: PoolType;
|
|
132
138
|
/**
|
|
133
139
|
* Pool information.
|
|
134
140
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IPool } from '../pool';
|
|
2
2
|
import type { IWorker } from '../worker';
|
|
3
|
-
import type { IWorkerChoiceStrategy,
|
|
3
|
+
import type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
5
5
|
* Worker choice strategy abstract base class.
|
|
6
6
|
*
|
|
@@ -16,7 +16,7 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
|
|
|
16
16
|
*/
|
|
17
17
|
private toggleFindLastFreeWorkerNodeKey;
|
|
18
18
|
/** @inheritDoc */
|
|
19
|
-
readonly
|
|
19
|
+
readonly taskStatistics: TaskStatistics;
|
|
20
20
|
/**
|
|
21
21
|
* Constructs a worker choice strategy bound to the pool.
|
|
22
22
|
*
|
|
@@ -24,7 +24,7 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
|
|
|
24
24
|
* @param opts - The worker choice strategy options.
|
|
25
25
|
*/
|
|
26
26
|
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
27
|
-
protected
|
|
27
|
+
protected setTaskStatistics(opts: WorkerChoiceStrategyOptions): void;
|
|
28
28
|
/** @inheritDoc */
|
|
29
29
|
abstract reset(): boolean;
|
|
30
30
|
/** @inheritDoc */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IPool } from '../pool';
|
|
2
2
|
import type { IWorker } from '../worker';
|
|
3
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
4
|
-
import type { IWorkerChoiceStrategy,
|
|
4
|
+
import type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
5
5
|
/**
|
|
6
6
|
* Selects the next worker with a fair share scheduling algorithm.
|
|
7
7
|
* Loosely modeled after the fair queueing algorithm: https://en.wikipedia.org/wiki/Fair_queuing.
|
|
@@ -12,7 +12,7 @@ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOpt
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class FairShareWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
14
14
|
/** @inheritDoc */
|
|
15
|
-
readonly
|
|
15
|
+
readonly taskStatistics: TaskStatistics;
|
|
16
16
|
/**
|
|
17
17
|
* Workers' virtual task end execution timestamp.
|
|
18
18
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IPool } from '../pool';
|
|
2
2
|
import type { IWorker } from '../worker';
|
|
3
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
4
|
-
import type { IWorkerChoiceStrategy,
|
|
4
|
+
import type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
5
5
|
/**
|
|
6
6
|
* Selects the least busy worker.
|
|
7
7
|
*
|
|
@@ -11,7 +11,7 @@ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOpt
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class LeastBusyWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
13
13
|
/** @inheritDoc */
|
|
14
|
-
readonly
|
|
14
|
+
readonly taskStatistics: TaskStatistics;
|
|
15
15
|
/** @inheritDoc */
|
|
16
16
|
constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
|
|
17
17
|
/** @inheritDoc */
|
|
@@ -62,7 +62,7 @@ export interface WorkerChoiceStrategyOptions {
|
|
|
62
62
|
*
|
|
63
63
|
* @internal
|
|
64
64
|
*/
|
|
65
|
-
export interface
|
|
65
|
+
export interface TaskStatistics {
|
|
66
66
|
/**
|
|
67
67
|
* Require tasks runtime.
|
|
68
68
|
*/
|
|
@@ -87,15 +87,19 @@ export interface RequiredStatistics {
|
|
|
87
87
|
* Require tasks median wait time.
|
|
88
88
|
*/
|
|
89
89
|
medWaitTime: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Event loop utilization.
|
|
92
|
+
*/
|
|
93
|
+
elu: boolean;
|
|
90
94
|
}
|
|
91
95
|
/**
|
|
92
96
|
* Worker choice strategy interface.
|
|
93
97
|
*/
|
|
94
98
|
export interface IWorkerChoiceStrategy {
|
|
95
99
|
/**
|
|
96
|
-
* Required tasks
|
|
100
|
+
* Required tasks statistics.
|
|
97
101
|
*/
|
|
98
|
-
readonly
|
|
102
|
+
readonly taskStatistics: TaskStatistics;
|
|
99
103
|
/**
|
|
100
104
|
* Resets strategy internals.
|
|
101
105
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IWorker } from '../worker';
|
|
2
2
|
import type { IPool } from '../pool';
|
|
3
3
|
import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
|
|
4
|
-
import type { IWorkerChoiceStrategy,
|
|
4
|
+
import type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
5
5
|
/**
|
|
6
6
|
* Selects the next worker with a weighted round robin scheduling algorithm.
|
|
7
7
|
* Loosely modeled after the weighted round robin queueing algorithm: https://en.wikipedia.org/wiki/Weighted_round_robin.
|
|
@@ -12,7 +12,7 @@ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOpt
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
|
|
14
14
|
/** @inheritDoc */
|
|
15
|
-
readonly
|
|
15
|
+
readonly taskStatistics: TaskStatistics;
|
|
16
16
|
/**
|
|
17
17
|
* Worker node id where the current task will be submitted.
|
|
18
18
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IPool } from '../pool';
|
|
2
2
|
import type { IWorker } from '../worker';
|
|
3
|
-
import type {
|
|
3
|
+
import type { TaskStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
|
|
4
4
|
/**
|
|
5
5
|
* The worker choice strategy context.
|
|
6
6
|
*
|
|
@@ -20,11 +20,11 @@ export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data =
|
|
|
20
20
|
*/
|
|
21
21
|
constructor(pool: IPool<Worker, Data, Response>, workerChoiceStrategy?: WorkerChoiceStrategy, opts?: WorkerChoiceStrategyOptions);
|
|
22
22
|
/**
|
|
23
|
-
* Gets the worker choice strategy in the context
|
|
23
|
+
* Gets the worker choice strategy task statistics in the context.
|
|
24
24
|
*
|
|
25
|
-
* @returns The
|
|
25
|
+
* @returns The task statistics.
|
|
26
26
|
*/
|
|
27
|
-
|
|
27
|
+
getTaskStatistics(): TaskStatistics;
|
|
28
28
|
/**
|
|
29
29
|
* Sets the worker choice strategy to use in the context.
|
|
30
30
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { FixedThreadPool, type
|
|
1
|
+
import { type PoolType } from '../pool';
|
|
2
|
+
import { FixedThreadPool, type ThreadPoolOptions } from './fixed';
|
|
3
3
|
/**
|
|
4
4
|
* A thread pool with a dynamic number of threads, but a guaranteed minimum number of threads.
|
|
5
5
|
*
|
|
@@ -12,7 +12,7 @@ import { FixedThreadPool, type ThreadWorkerWithMessageChannel } from './fixed';
|
|
|
12
12
|
* @since 0.0.1
|
|
13
13
|
*/
|
|
14
14
|
export declare class DynamicThreadPool<Data = unknown, Response = unknown> extends FixedThreadPool<Data, Response> {
|
|
15
|
-
readonly max: number;
|
|
15
|
+
protected readonly max: number;
|
|
16
16
|
/**
|
|
17
17
|
* Constructs a new poolifier dynamic thread pool.
|
|
18
18
|
*
|
|
@@ -21,11 +21,9 @@ export declare class DynamicThreadPool<Data = unknown, Response = unknown> exten
|
|
|
21
21
|
* @param filePath - Path to an implementation of a `ThreadWorker` file, which can be relative or absolute.
|
|
22
22
|
* @param opts - Options for this dynamic thread pool.
|
|
23
23
|
*/
|
|
24
|
-
constructor(min: number, max: number, filePath: string, opts?:
|
|
24
|
+
constructor(min: number, max: number, filePath: string, opts?: ThreadPoolOptions);
|
|
25
25
|
/** @inheritDoc */
|
|
26
|
-
get type(): PoolType;
|
|
27
|
-
/** @inheritDoc */
|
|
28
|
-
protected get full(): boolean;
|
|
26
|
+
protected get type(): PoolType;
|
|
29
27
|
/** @inheritDoc */
|
|
30
28
|
protected get maxSize(): number;
|
|
31
29
|
/** @inheritDoc */
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { MessageChannel, Worker } from 'node:worker_threads';
|
|
2
|
+
import { MessageChannel, Worker, type WorkerOptions } from 'node:worker_threads';
|
|
3
3
|
import type { Draft, MessageValue } from '../../utility-types';
|
|
4
4
|
import { AbstractPool } from '../abstract-pool';
|
|
5
|
-
import { type PoolOptions, type PoolType } from '../pool';
|
|
5
|
+
import { type PoolOptions, type PoolType, type WorkerType } from '../pool';
|
|
6
|
+
/**
|
|
7
|
+
* Options for a poolifier thread pool.
|
|
8
|
+
*/
|
|
9
|
+
export interface ThreadPoolOptions extends PoolOptions<Worker> {
|
|
10
|
+
/**
|
|
11
|
+
* Worker options.
|
|
12
|
+
*
|
|
13
|
+
* @see https://nodejs.org/api/worker_threads.html#new-workerfilename-options
|
|
14
|
+
*/
|
|
15
|
+
workerOptions?: WorkerOptions;
|
|
16
|
+
}
|
|
6
17
|
/**
|
|
7
18
|
* A thread worker with message channels for communication between main thread and thread worker.
|
|
8
19
|
*/
|
|
@@ -20,6 +31,7 @@ export type ThreadWorkerWithMessageChannel = Worker & Draft<MessageChannel>;
|
|
|
20
31
|
* @since 0.0.1
|
|
21
32
|
*/
|
|
22
33
|
export declare class FixedThreadPool<Data = unknown, Response = unknown> extends AbstractPool<ThreadWorkerWithMessageChannel, Data, Response> {
|
|
34
|
+
protected readonly opts: ThreadPoolOptions;
|
|
23
35
|
/**
|
|
24
36
|
* Constructs a new poolifier fixed thread pool.
|
|
25
37
|
*
|
|
@@ -27,7 +39,7 @@ export declare class FixedThreadPool<Data = unknown, Response = unknown> extends
|
|
|
27
39
|
* @param filePath - Path to an implementation of a `ThreadWorker` file, which can be relative or absolute.
|
|
28
40
|
* @param opts - Options for this fixed thread pool.
|
|
29
41
|
*/
|
|
30
|
-
constructor(numberOfThreads: number, filePath: string, opts?:
|
|
42
|
+
constructor(numberOfThreads: number, filePath: string, opts?: ThreadPoolOptions);
|
|
31
43
|
/** @inheritDoc */
|
|
32
44
|
protected isMain(): boolean;
|
|
33
45
|
/** @inheritDoc */
|
|
@@ -41,13 +53,13 @@ export declare class FixedThreadPool<Data = unknown, Response = unknown> extends
|
|
|
41
53
|
/** @inheritDoc */
|
|
42
54
|
protected afterWorkerSetup(worker: ThreadWorkerWithMessageChannel): void;
|
|
43
55
|
/** @inheritDoc */
|
|
44
|
-
get type(): PoolType;
|
|
56
|
+
protected get type(): PoolType;
|
|
57
|
+
/** @inheritDoc */
|
|
58
|
+
protected get worker(): WorkerType;
|
|
45
59
|
/** @inheritDoc */
|
|
46
60
|
protected get minSize(): number;
|
|
47
61
|
/** @inheritDoc */
|
|
48
62
|
protected get maxSize(): number;
|
|
49
63
|
/** @inheritDoc */
|
|
50
|
-
protected get full(): boolean;
|
|
51
|
-
/** @inheritDoc */
|
|
52
64
|
protected get busy(): boolean;
|
|
53
65
|
}
|
package/lib/pools/worker.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { EventLoopUtilization } from 'node:perf_hooks';
|
|
1
3
|
import type { CircularArray } from '../circular-array';
|
|
2
4
|
import type { Queue } from '../queue';
|
|
3
5
|
/**
|
|
@@ -32,9 +34,9 @@ export interface Task<Data = unknown> {
|
|
|
32
34
|
*/
|
|
33
35
|
readonly data?: Data;
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
37
|
+
* Timestamp.
|
|
36
38
|
*/
|
|
37
|
-
readonly
|
|
39
|
+
readonly timestamp?: number;
|
|
38
40
|
/**
|
|
39
41
|
* Message UUID.
|
|
40
42
|
*/
|
|
@@ -49,7 +51,7 @@ export interface TasksUsage {
|
|
|
49
51
|
/**
|
|
50
52
|
* Number of tasks executed.
|
|
51
53
|
*/
|
|
52
|
-
|
|
54
|
+
ran: number;
|
|
53
55
|
/**
|
|
54
56
|
* Number of tasks running.
|
|
55
57
|
*/
|
|
@@ -90,6 +92,10 @@ export interface TasksUsage {
|
|
|
90
92
|
* Number of tasks errored.
|
|
91
93
|
*/
|
|
92
94
|
error: number;
|
|
95
|
+
/**
|
|
96
|
+
* Event loop utilization.
|
|
97
|
+
*/
|
|
98
|
+
elu: EventLoopUtilization | undefined;
|
|
93
99
|
}
|
|
94
100
|
/**
|
|
95
101
|
* Worker interface.
|
package/lib/utility-types.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
+
/// <reference types="node" />
|
|
3
4
|
import type { Worker as ClusterWorker } from 'node:cluster';
|
|
4
5
|
import type { MessagePort } from 'node:worker_threads';
|
|
6
|
+
import type { EventLoopUtilization } from 'node:perf_hooks';
|
|
5
7
|
import type { KillBehavior } from './worker/worker-options';
|
|
6
8
|
import type { IWorker, Task } from './pools/worker';
|
|
7
9
|
/**
|
|
@@ -12,6 +14,14 @@ import type { IWorker, Task } from './pools/worker';
|
|
|
12
14
|
export type Draft<T> = {
|
|
13
15
|
-readonly [P in keyof T]?: T[P];
|
|
14
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Performance statistics computation.
|
|
19
|
+
*/
|
|
20
|
+
export interface WorkerStatistics {
|
|
21
|
+
runTime: boolean;
|
|
22
|
+
waitTime: boolean;
|
|
23
|
+
elu: boolean;
|
|
24
|
+
}
|
|
15
25
|
/**
|
|
16
26
|
* Message object that is passed between main worker and worker.
|
|
17
27
|
*
|
|
@@ -40,44 +50,19 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
|
|
|
40
50
|
* Wait time.
|
|
41
51
|
*/
|
|
42
52
|
readonly waitTime?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Event loop utilization.
|
|
55
|
+
*/
|
|
56
|
+
readonly elu?: EventLoopUtilization;
|
|
43
57
|
/**
|
|
44
58
|
* Reference to main worker.
|
|
45
59
|
*/
|
|
46
60
|
readonly parent?: MainWorker;
|
|
61
|
+
/**
|
|
62
|
+
* Whether to compute the given statistics or not.
|
|
63
|
+
*/
|
|
64
|
+
readonly statistics?: WorkerStatistics;
|
|
47
65
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Worker synchronous function that can be executed.
|
|
50
|
-
*
|
|
51
|
-
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
52
|
-
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
53
|
-
*/
|
|
54
|
-
export type WorkerSyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Response;
|
|
55
|
-
/**
|
|
56
|
-
* Worker asynchronous function that can be executed.
|
|
57
|
-
* This function must return a promise.
|
|
58
|
-
*
|
|
59
|
-
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
60
|
-
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
61
|
-
*/
|
|
62
|
-
export type WorkerAsyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Promise<Response>;
|
|
63
|
-
/**
|
|
64
|
-
* Worker function that can be executed.
|
|
65
|
-
* This function can be synchronous or asynchronous.
|
|
66
|
-
*
|
|
67
|
-
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
68
|
-
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
69
|
-
*/
|
|
70
|
-
export type WorkerFunction<Data = unknown, Response = unknown> = WorkerSyncFunction<Data, Response> | WorkerAsyncFunction<Data, Response>;
|
|
71
|
-
/**
|
|
72
|
-
* Worker functions that can be executed.
|
|
73
|
-
* This object can contain synchronous or asynchronous functions.
|
|
74
|
-
* The key is the name of the function.
|
|
75
|
-
* The value is the function itself.
|
|
76
|
-
*
|
|
77
|
-
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
78
|
-
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
79
|
-
*/
|
|
80
|
-
export type TaskFunctions<Data = unknown, Response = unknown> = Record<string, WorkerFunction<Data, Response>>;
|
|
81
66
|
/**
|
|
82
67
|
* An object holding the execution response promise resolve/reject callbacks.
|
|
83
68
|
*
|
|
@@ -2,11 +2,23 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
/// <reference types="node" />
|
|
5
|
+
/// <reference types="node" />
|
|
5
6
|
import { AsyncResource } from 'node:async_hooks';
|
|
6
7
|
import type { Worker } from 'node:cluster';
|
|
7
8
|
import type { MessagePort } from 'node:worker_threads';
|
|
8
|
-
import
|
|
9
|
-
import type {
|
|
9
|
+
import { type EventLoopUtilization } from 'node:perf_hooks';
|
|
10
|
+
import type { MessageValue, WorkerStatistics } from '../utility-types';
|
|
11
|
+
import { type WorkerOptions } from './worker-options';
|
|
12
|
+
import type { TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './worker-functions';
|
|
13
|
+
/**
|
|
14
|
+
* Task performance.
|
|
15
|
+
*/
|
|
16
|
+
export interface TaskPerformance {
|
|
17
|
+
timestamp: number;
|
|
18
|
+
waitTime?: number;
|
|
19
|
+
runTime?: number;
|
|
20
|
+
elu?: EventLoopUtilization;
|
|
21
|
+
}
|
|
10
22
|
/**
|
|
11
23
|
* Base class that implements some shared logic for all poolifier workers.
|
|
12
24
|
*
|
|
@@ -26,6 +38,10 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
26
38
|
* Timestamp of the last task processed by this worker.
|
|
27
39
|
*/
|
|
28
40
|
protected lastTaskTimestamp: number;
|
|
41
|
+
/**
|
|
42
|
+
* Performance statistics computation.
|
|
43
|
+
*/
|
|
44
|
+
protected statistics: WorkerStatistics;
|
|
29
45
|
/**
|
|
30
46
|
* Handler id of the `aliveInterval` worker alive check.
|
|
31
47
|
*/
|
|
@@ -96,4 +112,6 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
|
|
|
96
112
|
* @param name - Name of the function that will be returned.
|
|
97
113
|
*/
|
|
98
114
|
private getTaskFunction;
|
|
115
|
+
private beginTaskPerformance;
|
|
116
|
+
private endTaskPerformance;
|
|
99
117
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import type
|
|
3
|
-
import type { MessageValue
|
|
2
|
+
import { type Worker } from 'node:cluster';
|
|
3
|
+
import type { MessageValue } from '../utility-types';
|
|
4
4
|
import { AbstractWorker } from './abstract-worker';
|
|
5
5
|
import type { WorkerOptions } from './worker-options';
|
|
6
|
+
import type { TaskFunctions, WorkerFunction } from './worker-functions';
|
|
6
7
|
/**
|
|
7
8
|
* A cluster worker used by a poolifier `ClusterPool`.
|
|
8
9
|
*
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import type
|
|
3
|
-
import type { MessageValue
|
|
2
|
+
import { type MessagePort } from 'node:worker_threads';
|
|
3
|
+
import type { MessageValue } from '../utility-types';
|
|
4
4
|
import { AbstractWorker } from './abstract-worker';
|
|
5
5
|
import type { WorkerOptions } from './worker-options';
|
|
6
|
+
import type { TaskFunctions, WorkerFunction } from './worker-functions';
|
|
6
7
|
/**
|
|
7
8
|
* A thread worker used by a poolifier `ThreadPool`.
|
|
8
9
|
*
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker synchronous function that can be executed.
|
|
3
|
+
*
|
|
4
|
+
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
5
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
6
|
+
*/
|
|
7
|
+
export type WorkerSyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Response;
|
|
8
|
+
/**
|
|
9
|
+
* Worker asynchronous function that can be executed.
|
|
10
|
+
* This function must return a promise.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
13
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
14
|
+
*/
|
|
15
|
+
export type WorkerAsyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Promise<Response>;
|
|
16
|
+
/**
|
|
17
|
+
* Worker function that can be executed.
|
|
18
|
+
* This function can be synchronous or asynchronous.
|
|
19
|
+
*
|
|
20
|
+
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
21
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
22
|
+
*/
|
|
23
|
+
export type WorkerFunction<Data = unknown, Response = unknown> = WorkerSyncFunction<Data, Response> | WorkerAsyncFunction<Data, Response>;
|
|
24
|
+
/**
|
|
25
|
+
* Worker functions that can be executed.
|
|
26
|
+
* This object can contain synchronous or asynchronous functions.
|
|
27
|
+
* The key is the name of the function.
|
|
28
|
+
* The value is the function itself.
|
|
29
|
+
*
|
|
30
|
+
* @typeParam Data - Type of data sent to the worker. This can only be serializable data.
|
|
31
|
+
* @typeParam Response - Type of execution response. This can only be serializable data.
|
|
32
|
+
*/
|
|
33
|
+
export type TaskFunctions<Data = unknown, Response = unknown> = Record<string, WorkerFunction<Data, Response>>;
|
|
@@ -23,7 +23,7 @@ export type KillBehavior = keyof typeof KillBehaviors;
|
|
|
23
23
|
* @param value - Any value.
|
|
24
24
|
* @returns `true` if `value` was strictly equals to `killBehavior`, otherwise `false`.
|
|
25
25
|
*/
|
|
26
|
-
export declare
|
|
26
|
+
export declare const isKillBehavior: <KB extends "SOFT" | "HARD">(killBehavior: KB, value: unknown) => value is KB;
|
|
27
27
|
/**
|
|
28
28
|
* Options for workers.
|
|
29
29
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poolifier",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.4",
|
|
4
4
|
"description": "A fast, easy to use Node.js Worker Thread Pool and Cluster Pool implementation",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"volta": {
|
|
28
28
|
"node": "20.2.0",
|
|
29
|
-
"pnpm": "8.6.
|
|
29
|
+
"pnpm": "8.6.1"
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
@@ -85,17 +85,17 @@
|
|
|
85
85
|
"@rollup/plugin-terser": "^0.4.3",
|
|
86
86
|
"@rollup/plugin-typescript": "^11.1.1",
|
|
87
87
|
"@types/node": "^20.2.5",
|
|
88
|
-
"@typescript-eslint/eslint-plugin": "^5.59.
|
|
89
|
-
"@typescript-eslint/parser": "^5.59.
|
|
88
|
+
"@typescript-eslint/eslint-plugin": "^5.59.9",
|
|
89
|
+
"@typescript-eslint/parser": "^5.59.9",
|
|
90
90
|
"benny": "^3.7.1",
|
|
91
91
|
"c8": "^7.14.0",
|
|
92
|
-
"eslint": "^8.
|
|
92
|
+
"eslint": "^8.42.0",
|
|
93
93
|
"eslint-config-standard": "^17.1.0",
|
|
94
94
|
"eslint-config-standard-with-typescript": "^35.0.0",
|
|
95
95
|
"eslint-define-config": "^1.20.0",
|
|
96
96
|
"eslint-import-resolver-typescript": "^3.5.5",
|
|
97
97
|
"eslint-plugin-import": "^2.27.5",
|
|
98
|
-
"eslint-plugin-jsdoc": "^46.
|
|
98
|
+
"eslint-plugin-jsdoc": "^46.2.6",
|
|
99
99
|
"eslint-plugin-n": "^16.0.0",
|
|
100
100
|
"eslint-plugin-promise": "^6.1.1",
|
|
101
101
|
"eslint-plugin-spellcheck": "^0.0.20",
|
|
@@ -107,15 +107,15 @@
|
|
|
107
107
|
"mocha": "^10.2.0",
|
|
108
108
|
"mochawesome": "^7.1.3",
|
|
109
109
|
"prettier": "^2.8.8",
|
|
110
|
-
"release-it": "^15.
|
|
111
|
-
"rollup": "^3.
|
|
110
|
+
"release-it": "^15.11.0",
|
|
111
|
+
"rollup": "^3.24.0",
|
|
112
112
|
"rollup-plugin-analyzer": "^4.0.0",
|
|
113
113
|
"rollup-plugin-command": "^1.1.3",
|
|
114
114
|
"rollup-plugin-delete": "^2.0.0",
|
|
115
115
|
"sinon": "^15.1.0",
|
|
116
116
|
"source-map-support": "^0.5.21",
|
|
117
117
|
"ts-standard": "^12.0.2",
|
|
118
|
-
"typedoc": "^0.24.
|
|
118
|
+
"typedoc": "^0.24.8",
|
|
119
119
|
"typescript": "^5.1.3"
|
|
120
120
|
},
|
|
121
121
|
"scripts": {
|