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 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
- ### `pool = new FixedThreadPool/FixedClusterPool(numberOfThreads/numberOfWorkers, filePath, opts)`
153
+ ### Pool options
153
154
 
154
- `numberOfThreads/numberOfWorkers` (mandatory) Number of workers for this pool
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 robbin fashion
165
- - `WorkerChoiceStrategies.LEAST_USED`: Submit tasks to the least used worker
166
- - `WorkerChoiceStrategies.LEAST_BUSY`: Submit tasks to the least busy worker
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) Same as FixedThreadPool/FixedClusterPool
201
- `opts` (optional) Same as FixedThreadPool/FixedClusterPool
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(s) that you want to execute on the worker
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.4.x](https://github.com/orgs/poolifier/projects/1), propose an idea, a fix, an improvement.
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 { PoolEvents, PoolTypes } from './pools/pool';
6
- export type { IPool, PoolEmitter, PoolEvent, PoolInfo, PoolOptions, PoolType, TasksQueueOptions } from './pools/pool';
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, RequiredStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types';
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 { ThreadWorkerWithMessageChannel } from './pools/thread/fixed';
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 { Draft, MessageValue, PromiseResponseWrapper, TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './utility-types';
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 abstract get full(): boolean;
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 { ClusterSettings, Worker } from 'node:cluster';
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
  }
@@ -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, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
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 requiredStatistics: RequiredStatistics;
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 setRequiredStatistics(opts: WorkerChoiceStrategyOptions): void;
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, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
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 requiredStatistics: RequiredStatistics;
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, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
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 requiredStatistics: RequiredStatistics;
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 RequiredStatistics {
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 usage statistics.
100
+ * Required tasks statistics.
97
101
  */
98
- readonly requiredStatistics: RequiredStatistics;
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, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
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 requiredStatistics: RequiredStatistics;
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 { RequiredStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
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 required statistics.
23
+ * Gets the worker choice strategy task statistics in the context.
24
24
  *
25
- * @returns The required statistics.
25
+ * @returns The task statistics.
26
26
  */
27
- getRequiredStatistics(): RequiredStatistics;
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 PoolOptions, type PoolType } from '../pool';
2
- import { FixedThreadPool, type ThreadWorkerWithMessageChannel } from './fixed';
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?: PoolOptions<ThreadWorkerWithMessageChannel>);
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?: PoolOptions<ThreadWorkerWithMessageChannel>);
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
  }
@@ -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
- * Submission timestamp.
37
+ * Timestamp.
36
38
  */
37
- readonly submissionTimestamp?: number;
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
- run: number;
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.
@@ -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 type { MessageValue, TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from '../utility-types';
9
- import type { WorkerOptions } from './worker-options';
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 { Worker } from 'node:cluster';
3
- import type { MessageValue, TaskFunctions, WorkerFunction } from '../utility-types';
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 { MessagePort } from 'node:worker_threads';
3
- import type { MessageValue, TaskFunctions, WorkerFunction } from '../utility-types';
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 function isKillBehavior<KB extends KillBehavior>(killBehavior: KB, value: unknown): value is KB;
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.2",
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.0"
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.8",
89
- "@typescript-eslint/parser": "^5.59.8",
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.41.0",
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.1.0",
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.10.5",
111
- "rollup": "^3.23.0",
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.7",
118
+ "typedoc": "^0.24.8",
119
119
  "typescript": "^5.1.3"
120
120
  },
121
121
  "scripts": {