poolifier 2.5.4 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -161,11 +161,12 @@ An object with these properties:
161
161
  - `workerChoiceStrategy` (optional) - The worker choice strategy to use in this pool:
162
162
 
163
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
164
+ - `WorkerChoiceStrategies.LEAST_USED`: Submit tasks to the worker with the minimum number of executed, executing and queued tasks
165
+ - `WorkerChoiceStrategies.LEAST_BUSY`: Submit tasks to the worker with the minimum tasks total execution and wait time
166
+ - `WorkerChoiceStrategies.LEAST_ELU`: Submit tasks to the worker with the minimum event loop utilization (ELU) (experimental)
166
167
  - `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`: Submit tasks to worker by using a weighted round robin scheduling algorithm based on tasks execution time
167
168
  - `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
169
+ - `WorkerChoiceStrategies.FAIR_SHARE`: Submit tasks to worker by using a fair share tasks scheduling algorithm based on tasks execution time or ELU
169
170
 
170
171
  `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`, `WorkerChoiceStrategies.INTERLEAVED_WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks.
171
172
  Default: `WorkerChoiceStrategies.ROUND_ROBIN`
@@ -173,10 +174,13 @@ An object with these properties:
173
174
  - `workerChoiceStrategyOptions` (optional) - The worker choice strategy options object to use in this pool.
174
175
  Properties:
175
176
 
176
- - `medRunTime` (optional) - Use the tasks median runtime instead of the tasks average runtime in worker choice strategies.
177
- - `weights` (optional) - The worker weights to use in the weighted round robin worker choice strategy: `{ 0: 200, 1: 300, ..., n: 100 }`
177
+ - `measurement` (optional) - The measurement to use in worker choice strategies: `runTime`, `waitTime` or `elu`.
178
+ - `runTime` (optional) - Use the tasks median runtime instead of the tasks average runtime in worker choice strategies.
179
+ - `waitTime` (optional) - Use the tasks median wait time instead of the tasks average wait time in worker choice strategies.
180
+ - `elu` (optional) - Use the tasks median ELU instead of the tasks average ELU in worker choice strategies.
181
+ - `weights` (optional) - The worker weights to use in the weighted round robin worker choice strategy: `{ 0: 200, 1: 300, ..., n: 100 }`.
178
182
 
179
- Default: `{ medRunTime: false }`
183
+ Default: `{ runTime: { median: false }, waitTime: { median: false }, elu: { median: false } }`
180
184
 
181
185
  - `restartWorkerOnError` (optional) - Restart worker on uncaught error in this pool.
182
186
  Default: `true`
@@ -192,15 +196,15 @@ An object with these properties:
192
196
 
193
197
  Default: `{ concurrency: 1 }`
194
198
 
195
- #### Thread pool specific options
199
+ #### Thread pool specific options
196
200
 
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.
201
+ - `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
202
 
199
- #### Cluster pool specific options
203
+ #### Cluster pool specific options
200
204
 
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.
205
+ - `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
206
 
203
- - `settings` (optional) - An object with the cluster settings. See [cluster](https://nodejs.org/api/cluster.html#cluster_cluster_settings) for more details.
207
+ - `settings` (optional) - An object with the cluster settings. See [cluster](https://nodejs.org/api/cluster.html#cluster_cluster_settings) for more details.
204
208
 
205
209
  ### `pool = new FixedThreadPool/FixedClusterPool(numberOfThreads/numberOfWorkers, filePath, opts)`
206
210
 
@@ -238,8 +242,8 @@ This method will call the terminate method on each worker.
238
242
  Default: `60000`
239
243
 
240
244
  - `killBehavior` (optional) - Dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
241
- **KillBehaviors.SOFT**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted.
242
- **KillBehaviors.HARD**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
245
+ **KillBehaviors.SOFT**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still executing, then the worker **won't** be deleted.
246
+ **KillBehaviors.HARD**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still executing, then the worker will be deleted.
243
247
  This option only apply to the newly created workers.
244
248
  Default: `KillBehaviors.SOFT`
245
249
 
package/lib/index.d.ts CHANGED
@@ -3,18 +3,18 @@ export { DynamicClusterPool } from './pools/cluster/dynamic';
3
3
  export { FixedClusterPool, type ClusterPoolOptions } from './pools/cluster/fixed';
4
4
  export { PoolEvents, PoolTypes, WorkerTypes } from './pools/pool';
5
5
  export type { IPool, PoolEmitter, PoolEvent, PoolInfo, PoolOptions, PoolType, TasksQueueOptions, WorkerType } from './pools/pool';
6
- export type { ErrorHandler, ExitHandler, IWorker, MessageHandler, OnlineHandler, Task, TasksUsage, WorkerNode } from './pools/worker';
7
- export { WorkerChoiceStrategies } from './pools/selection-strategies/selection-strategies-types';
8
- export type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types';
6
+ export type { ErrorHandler, EventLoopUtilizationMeasurementStatistics, ExitHandler, IWorker, MeasurementStatistics, MessageHandler, OnlineHandler, Task, TaskStatistics, WorkerNode, WorkerUsage } from './pools/worker';
7
+ export { Measurements, WorkerChoiceStrategies } from './pools/selection-strategies/selection-strategies-types';
8
+ export type { IWorkerChoiceStrategy, Measurement, MeasurementOptions, MeasurementStatisticsRequirements, TaskStatisticsRequirements, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types';
9
9
  export type { WorkerChoiceStrategyContext } from './pools/selection-strategies/worker-choice-strategy-context';
10
10
  export { DynamicThreadPool } from './pools/thread/dynamic';
11
11
  export { FixedThreadPool, type ThreadPoolOptions, type ThreadWorkerWithMessageChannel } from './pools/thread/fixed';
12
- export type { AbstractWorker, TaskPerformance } from './worker/abstract-worker';
12
+ export type { AbstractWorker } from './worker/abstract-worker';
13
13
  export { ClusterWorker } from './worker/cluster-worker';
14
14
  export { ThreadWorker } from './worker/thread-worker';
15
15
  export { KillBehaviors } from './worker/worker-options';
16
16
  export type { KillBehavior, WorkerOptions } from './worker/worker-options';
17
17
  export type { TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './worker/worker-functions';
18
- export type { Draft, MessageValue, PromiseResponseWrapper, WorkerStatistics } from './utility-types';
18
+ export type { Draft, MessageValue, PromiseResponseWrapper, TaskError, TaskPerformance, WorkerStatistics } from './utility-types';
19
19
  export type { CircularArray } from './circular-array';
20
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"),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;
1
+ "use strict";var e=require("node:events"),t=require("node:cluster"),r=require("node:crypto"),s=require("node:perf_hooks"),i=require("node:os"),o=require("node:worker_threads"),a=require("node:async_hooks");const n=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={runTime:{median:!1},waitTime:{median:!1},elu:{median:!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},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class p 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 T=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),f=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class W{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=d){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){e=e??d,this.setTaskStatisticsRequirements(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/i.cpus().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].workerUsage.tasks.executing)return e;return-1}}class y extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=d){super(e,t),this.setTaskStatisticsRequirements(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.opts.measurement===f.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends W{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=d){super(e,t),this.setTaskStatisticsRequirements(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 N extends W{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=d){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e,t=1/0;for(const[r,s]of this.pool.workerNodes.entries()){const i=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===i)return r;i<t&&(t=i,e=r)}return e}remove(){return!0}}class x extends W{constructor(e,t=d){super(e,t),this.setTaskStatisticsRequirements(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.workerUsage.tasks,o=i.executed+i.executing+i.queued;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class v extends W{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=d){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e,t=1/0;for(const[r,s]of this.pool.workerNodes.entries()){const i=s.workerUsage,o=i.elu?.active.aggregate??0;if(0===o)return r;o<t&&(t=o,e=r)}return e}remove(){return!0}}class R extends W{nextWorkerNodeId=0;constructor(e,t=d){super(e,t),this.setTaskStatisticsRequirements(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{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=d){super(e,t),this.setTaskStatisticsRequirements(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 I{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=T.ROUND_ROBIN,r=d){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[T.ROUND_ROBIN,new(R.bind(this))(e,r)],[T.LEAST_USED,new(x.bind(this))(e,r)],[T.LEAST_BUSY,new(N.bind(this))(e,r)],[T.LEAST_ELU,new(v.bind(this))(e,r)],[T.FAIR_SHARE,new(y.bind(this))(e,r)],[T.WEIGHTED_ROUND_ROBIN,new(E.bind(this))(e,r)],[T.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,r)]])}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}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 b{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),!0===this.opts.enableEvents&&(this.emitter=new u),this.workerChoiceStrategyContext=new I(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===n.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??T.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(T).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,worker:this.worker,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),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,this.getWorkerUsage(e.worker)),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.workerUsage.tasks.executing))}async execute(e,t){const i=s.performance.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.workerChoiceStrategyContext.update(o),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,t){const r=this.workerNodes[e].workerUsage;++r.tasks.executing,this.updateWaitTimeWorkerUsage(r,t)}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage,s=r.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed,this.updateRunTimeWorkerUsage(r,t),this.updateEluWorkerUsage(r,t)}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/e.tasks.executed),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=l(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const r=s.performance.now(),i=r-(t.timestamp??r);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/e.tasks.executed),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=l(e.waitTime.history)))}updateEluWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate&&(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed&&(e.elu.idle.average=e.elu.idle.aggregate/e.tasks.executed,e.elu.active.average=e.elu.active.aggregate/e.tasks.executed),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=l(e.elu.idle.history),e.elu.active.median=l(e.elu.active.history)))}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=m.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].workerUsage.tasks.executing)&&(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??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.taskError?(t.reject(e.taskError.message),null!=this.emitter&&this.emitter.emit(k.taskError,e.taskError)):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(k.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(k.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(e),tasksQueue:new w})}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),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.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){return{tasks:this.getTaskStatistics(e),runTime:{aggregate:0,average:0,median:0,history:new p},waitTime:{aggregate:0,average:0,median:0,history:new p},elu:{idle:{aggregate:0,average:0,median:0,history:new p},active:{aggregate:0,average:0,median:0,history:new p},utilization:0}}}getTaskStatistics(e){const t=this.workerNodes[this.getWorkerNodeKey(e)]?.tasksQueue?.size;return{executed:0,executing:0,get queued(){return t??0},failed:0}}}class O extends b{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 worker(){return h.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class C extends b{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}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:r}=new o.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return n.fixed}get worker(){return h.thread}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}const q="default",z=6e4,U=m.SOFT;class A extends a.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,r,i,o={killBehavior:U,maxInactiveTime:z}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=s.performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??z)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??U,this.opts.maxInactiveTime=e.maxInactiveTime??z,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(q,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(q,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()):null!=e.statistics&&(this.statistics=e.statistics)}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){s.performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??z)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let r=this.beginTaskPerformance();const s=e(t.data);r=this.endTaskPerformance(r),this.sendToMainWorker({data:s,taskPerformance:r,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({taskError:{message:r,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=s.performance.now())}}runAsync(e,t){let r=this.beginTaskPerformance();e(t.data).then((e=>(r=this.endTaskPerformance(r),this.sendToMainWorker({data:e,taskPerformance:r,id:t.id}),null))).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({taskError:{message:r,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=s.performance.now())})).catch(c)}getTaskFunction(e){e=e??q;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return{timestamp:s.performance.now(),...this.statistics.elu&&{elu:s.performance.eventLoopUtilization()}}}endTaskPerformance(e){return{...e,...this.statistics.runTime&&{runTime:s.performance.now()-e.timestamp},...this.statistics.elu&&{elu:s.performance.eventLoopUtilization(e.elu)}}}}exports.ClusterWorker=class extends A{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 O{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends C{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=O,exports.FixedThreadPool=C,exports.KillBehaviors=m,exports.Measurements=f,exports.PoolEvents=k,exports.PoolTypes=n,exports.ThreadWorker=class extends A{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=T,exports.WorkerTypes=h;
package/lib/index.mjs CHANGED
@@ -1 +1 @@
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
+ import e from"node:events";import t from"node:cluster";import r from"node:crypto";import{performance as s}from"node:perf_hooks";import{cpus as i}from"node:os";import{isMainThread as o,Worker as a,SHARE_ENV as n,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 g=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),m=Object.freeze((()=>{})),p={runTime:{median:!1},waitTime:{median:!1},elu:{median:!1}},w=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},T=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),f=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 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",LEAST_ELU:"LEAST_ELU",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"}),N=Object.freeze({runTime:"runTime",waitTime:"waitTime",elu:"elu"});class x{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=p){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setTaskStatisticsRequirements(e){this.taskStatisticsRequirements.runTime.average&&!0===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!1,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.runTime.median&&!1===e.runTime?.median&&(this.taskStatisticsRequirements.runTime.average=!0,this.taskStatisticsRequirements.runTime.median=e.runTime.median),this.taskStatisticsRequirements.waitTime.average&&!0===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!1,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.waitTime.median&&!1===e.waitTime?.median&&(this.taskStatisticsRequirements.waitTime.average=!0,this.taskStatisticsRequirements.waitTime.median=e.waitTime.median),this.taskStatisticsRequirements.elu.average&&!0===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!1,this.taskStatisticsRequirements.elu.median=e.elu.median),this.taskStatisticsRequirements.elu.median&&!1===e.elu?.median&&(this.taskStatisticsRequirements.elu.average=!0,this.taskStatisticsRequirements.elu.median=e.elu.median)}setOptions(e){e=e??p,this.setTaskStatisticsRequirements(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.taskStatisticsRequirements.runTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskWaitTime(e){return this.taskStatisticsRequirements.waitTime.median?this.pool.workerNodes[e].workerUsage.runTime.median:this.pool.workerNodes[e].workerUsage.runTime.average}getWorkerTaskElu(e){return this.taskStatisticsRequirements.elu.median?this.pool.workerNodes[e].workerUsage.elu.active.median:this.pool.workerNodes[e].workerUsage.elu.active.average}computeDefaultWorkerWeight(){let e=0;for(const t of i()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/i().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.workerUsage.tasks.executing))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].workerUsage.tasks.executing)return e;return-1}}class v extends x{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!0,median:!1}};workersVirtualTaskEndTimestamp=[];constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(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.opts.measurement===N.elu?this.getWorkerTaskElu(e):this.getWorkerTaskRunTime(e))}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class R extends x{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(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 E extends x{taskStatisticsRequirements={runTime:{aggregate:!0,average:!1,median:!1},waitTime:{aggregate:!0,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e,t=1/0;for(const[r,s]of this.pool.workerNodes.entries()){const i=s.workerUsage.runTime.aggregate+s.workerUsage.waitTime.aggregate;if(0===i)return r;i<t&&(t=i,e=r)}return e}remove(){return!0}}class I extends x{constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(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.workerUsage.tasks,o=i.executed+i.executing+i.queued;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class b extends x{taskStatisticsRequirements={runTime:{aggregate:!1,average:!1,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!0,average:!1,median:!1}};constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(this.opts)}reset(){return!0}update(){return!0}choose(){let e,t=1/0;for(const[r,s]of this.pool.workerNodes.entries()){const i=s.workerUsage,o=i.elu?.active.aggregate??0;if(0===o)return r;o<t&&(t=o,e=r)}return e}remove(){return!0}}class O extends x{nextWorkerNodeId=0;constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(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 C extends x{taskStatisticsRequirements={runTime:{aggregate:!0,average:!0,median:!1},waitTime:{aggregate:!1,average:!1,median:!1},elu:{aggregate:!1,average:!1,median:!1}};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=p){super(e,t),this.setTaskStatisticsRequirements(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 q{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=S.ROUND_ROBIN,r=p){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[S.ROUND_ROBIN,new(O.bind(this))(e,r)],[S.LEAST_USED,new(I.bind(this))(e,r)],[S.LEAST_BUSY,new(E.bind(this))(e,r)],[S.LEAST_ELU,new(b.bind(this))(e,r)],[S.FAIR_SHARE,new(v.bind(this))(e,r)],[S.WEIGHTED_ROUND_ROBIN,new(C.bind(this))(e,r)],[S.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(R.bind(this))(e,r)]])}getTaskStatisticsRequirements(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).taskStatisticsRequirements}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 z{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),!0===this.opts.enableEvents&&(this.emitter=new l),this.workerChoiceStrategyContext=new q(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(!T(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??p,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(!T(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&&!T(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.workerUsage.tasks.executing?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.workerUsage.tasks.executing>0?e+1:e),0),executedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executed),0),executingTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.executing),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0),failedTasks:this.workerNodes.reduce(((e,t)=>e+t.workerUsage.tasks.failed),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,this.getWorkerUsage(e.worker)),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.workerUsage.tasks.executing))}async execute(e,t){const i=s.now(),o=this.chooseWorkerNode(),a={name:t,data:e??{},timestamp:i,id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(a.id,{resolve:e,reject:t,worker:this.workerNodes[o].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[o].workerUsage.tasks.executing>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(o,a):this.executeTask(o,a),this.workerChoiceStrategyContext.update(o),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,t){const r=this.workerNodes[e].workerUsage;++r.tasks.executing,this.updateWaitTimeWorkerUsage(r,t)}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].workerUsage,s=r.tasks;--s.executing,++s.executed,null!=t.taskError&&++s.failed,this.updateRunTimeWorkerUsage(r,t),this.updateEluWorkerUsage(r,t)}updateRunTimeWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.aggregate&&(e.runTime.aggregate+=t.taskPerformance?.runTime??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.average&&0!==e.tasks.executed&&(e.runTime.average=e.runTime.aggregate/e.tasks.executed),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().runTime.median&&null!=t.taskPerformance?.runTime&&(e.runTime.history.push(t.taskPerformance.runTime),e.runTime.median=w(e.runTime.history)))}updateWaitTimeWorkerUsage(e,t){const r=s.now(),i=r-(t.timestamp??r);this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.aggregate&&(e.waitTime.aggregate+=i??0,this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.average&&0!==e.tasks.executed&&(e.waitTime.average=e.waitTime.aggregate/e.tasks.executed),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().waitTime.median&&null!=i&&(e.waitTime.history.push(i),e.waitTime.median=w(e.waitTime.history)))}updateEluWorkerUsage(e,t){this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate&&(null!=e.elu&&null!=t.taskPerformance?.elu?(e.elu.idle.aggregate+=t.taskPerformance.elu.idle,e.elu.active.aggregate+=t.taskPerformance.elu.active,e.elu.utilization=(e.elu.utilization+t.taskPerformance.elu.utilization)/2):null!=t.taskPerformance?.elu&&(e.elu.idle.aggregate=t.taskPerformance.elu.idle,e.elu.active.aggregate=t.taskPerformance.elu.active,e.elu.utilization=t.taskPerformance.elu.utilization),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.average&&0!==e.tasks.executed&&(e.elu.idle.average=e.elu.idle.aggregate/e.tasks.executed,e.elu.active.average=e.elu.active.aggregate/e.tasks.executed),this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.median&&null!=t.taskPerformance?.elu&&(e.elu.idle.history.push(t.taskPerformance.elu.idle),e.elu.active.history.push(t.taskPerformance.elu.active),e.elu.idle.median=w(e.elu.idle.history),e.elu.active.median=w(e.elu.active.history)))}chooseWorkerNode(){let e;if(this.type===c.dynamic&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const r=this.getWorkerNodeKey(t);var s;s=f.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].workerUsage.tasks.executing)&&(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??m),e.on("error",this.opts.errorHandler??m),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(g.error,e)})),e.on("error",(()=>{!0===this.opts.restartWorkerOnError&&this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??m),e.on("exit",this.opts.exitHandler??m),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.taskError?(t.reject(e.taskError.message),null!=this.emitter&&this.emitter.emit(g.taskError,e.taskError)):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(g.busy,this.info),this.type===c.dynamic&&this.full&&this.emitter?.emit(g.full,this.info))}setWorkerNodeTasksUsage(e,t){e.workerUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,workerUsage:this.getWorkerUsage(e),tasksQueue:new y})}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e,t),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.getTaskStatisticsRequirements().runTime.aggregate,elu:this.workerChoiceStrategyContext.getTaskStatisticsRequirements().elu.aggregate}})}getWorkerUsage(e){return{tasks:this.getTaskStatistics(e),runTime:{aggregate:0,average:0,median:0,history:new W},waitTime:{aggregate:0,average:0,median:0,history:new W},elu:{idle:{aggregate:0,average:0,median:0,history:new W},active:{aggregate:0,average:0,median:0,history:new W},utilization:0}}}getTaskStatistics(e){const t=this.workerNodes[this.getWorkerNodeKey(e)]?.tasksQueue?.size;return{executed:0,executing:0,get queued(){return t??0},failed:0}}}class U extends z{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 c.fixed}get worker(){return d.cluster}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get busy(){return this.internalBusy()}}class A extends U{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return c.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}class Q extends z{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}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 a(this.filePath,{env:n,...this.opts.workerOptions})}afterWorkerSetup(e){const{port1:t,port2:r}=new h;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,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 F extends Q{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return c.dynamic}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}const M="default",P=6e4,_=f.SOFT;class L extends k{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;statistics;aliveInterval;constructor(e,t,r,i,o={killBehavior:_,maxInactiveTime:P}){super(e),this.isMain=t,this.mainWorker=i,this.opts=o,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=s.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??P)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??_,this.opts.maxInactiveTime=e.maxInactiveTime??P,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(M,e.bind(this));else{if(!T(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(M,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()):null!=e.statistics&&(this.statistics=e.statistics)}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){s.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??P)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{let r=this.beginTaskPerformance();const s=e(t.data);r=this.endTaskPerformance(r),this.sendToMainWorker({data:s,taskPerformance:r,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({taskError:{message:r,data:t.data},id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=s.now())}}runAsync(e,t){let r=this.beginTaskPerformance();e(t.data).then((e=>(r=this.endTaskPerformance(r),this.sendToMainWorker({data:e,taskPerformance:r,id:t.id}),null))).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({taskError:{message:r,data:t.data},id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=s.now())})).catch(m)}getTaskFunction(e){e=e??M;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}beginTaskPerformance(){return{timestamp:s.now(),...this.statistics.elu&&{elu:s.eventLoopUtilization()}}}endTaskPerformance(e){return{...e,...this.statistics.runTime&&{runTime:s.now()-e.timestamp},...this.statistics.elu&&{elu:s.eventLoopUtilization(e.elu)}}}}class V extends L{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 L{constructor(e,t={}){super("worker-thread-pool:poolifier",o,e,u,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{V as ClusterWorker,A as DynamicClusterPool,F as DynamicThreadPool,U as FixedClusterPool,Q as FixedThreadPool,f as KillBehaviors,N as Measurements,g as PoolEvents,c as PoolTypes,D as ThreadWorker,S as WorkerChoiceStrategies,d as WorkerTypes};
@@ -1,6 +1,6 @@
1
1
  import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
2
2
  import { type IPool, PoolEmitter, type PoolInfo, type PoolOptions, type PoolType, type TasksQueueOptions, type WorkerType } from './pool';
3
- import type { IWorker, WorkerNode } from './worker';
3
+ import type { IWorker, Task, 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';
6
6
  /**
@@ -29,8 +29,6 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
29
29
  protected promiseResponseMap: Map<string, PromiseResponseWrapper<Worker, Response>>;
30
30
  /**
31
31
  * Worker choice strategy context referencing a worker choice algorithm implementation.
32
- *
33
- * Default to a round robin algorithm.
34
32
  */
35
33
  protected workerChoiceStrategyContext: WorkerChoiceStrategyContext<Worker, Data, Response>;
36
34
  /**
@@ -122,8 +120,9 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
122
120
  * Can be overridden.
123
121
  *
124
122
  * @param workerNodeKey - The worker node key.
123
+ * @param task - The task to execute.
125
124
  */
126
- protected beforeTaskExecutionHook(workerNodeKey: number): void;
125
+ protected beforeTaskExecutionHook(workerNodeKey: number, task: Task<Data>): void;
127
126
  /**
128
127
  * Hook executed after the worker task execution.
129
128
  * Can be overridden.
@@ -132,9 +131,9 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
132
131
  * @param message - The received message.
133
132
  */
134
133
  protected afterTaskExecutionHook(worker: Worker, message: MessageValue<Response>): void;
135
- private updateRunTimeTasksUsage;
136
- private updateWaitTimeTasksUsage;
137
- private updateEluTasksUsage;
134
+ private updateRunTimeWorkerUsage;
135
+ private updateWaitTimeWorkerUsage;
136
+ private updateEluWorkerUsage;
138
137
  /**
139
138
  * Chooses a worker node for the next task.
140
139
  *
@@ -186,7 +185,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
186
185
  * Sets the given worker node its tasks usage in the pool.
187
186
  *
188
187
  * @param workerNode - The worker node.
189
- * @param tasksUsage - The worker node tasks usage.
188
+ * @param workerUsage - The worker usage.
190
189
  */
191
190
  private setWorkerNodeTasksUsage;
192
191
  /**
@@ -196,15 +195,6 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
196
195
  * @returns The worker nodes length.
197
196
  */
198
197
  private pushWorkerNode;
199
- /**
200
- * Sets the given worker in the pool worker nodes.
201
- *
202
- * @param workerNodeKey - The worker node key.
203
- * @param worker - The worker.
204
- * @param tasksUsage - The worker tasks usage.
205
- * @param tasksQueue - The worker task queue.
206
- */
207
- private setWorkerNode;
208
198
  /**
209
199
  * Removes the given worker from the pool worker nodes.
210
200
  *
@@ -218,4 +208,6 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
218
208
  private flushTasksQueue;
219
209
  private flushTasksQueues;
220
210
  private setWorkerStatistics;
211
+ private getWorkerUsage;
212
+ private getTaskStatistics;
221
213
  }
@@ -12,7 +12,7 @@ export interface ClusterPoolOptions extends PoolOptions<Worker> {
12
12
  *
13
13
  * @see https://nodejs.org/api/cluster.html#cluster_cluster_fork_env
14
14
  */
15
- env?: any;
15
+ env?: Record<string, unknown>;
16
16
  /**
17
17
  * Cluster settings.
18
18
  *
@@ -25,8 +25,6 @@ export interface ClusterPoolOptions extends PoolOptions<Worker> {
25
25
  *
26
26
  * It is possible to perform tasks in sync or asynchronous mode as you prefer.
27
27
  *
28
- * This pool selects the workers in a round robin fashion.
29
- *
30
28
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
31
29
  * @typeParam Response - Type of execution response. This can only be serializable data.
32
30
  * @author [Christopher Quadflieg](https://github.com/Shinigami92)
@@ -59,9 +59,11 @@ export interface PoolInfo {
59
59
  workerNodes: number;
60
60
  idleWorkerNodes: number;
61
61
  busyWorkerNodes: number;
62
- runningTasks: number;
62
+ executedTasks: number;
63
+ executingTasks: number;
63
64
  queuedTasks: number;
64
65
  maxQueuedTasks: number;
66
+ failedTasks: number;
65
67
  }
66
68
  /**
67
69
  * Worker tasks queue options.
@@ -1,6 +1,6 @@
1
1
  import type { IPool } from '../pool';
2
2
  import type { IWorker } from '../worker';
3
- import type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
3
+ import type { IWorkerChoiceStrategy, TaskStatisticsRequirements, 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 taskStatistics: TaskStatistics;
19
+ readonly taskStatisticsRequirements: TaskStatisticsRequirements;
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 setTaskStatistics(opts: WorkerChoiceStrategyOptions): void;
27
+ protected setTaskStatisticsRequirements(opts: WorkerChoiceStrategyOptions): void;
28
28
  /** @inheritDoc */
29
29
  abstract reset(): boolean;
30
30
  /** @inheritDoc */
@@ -43,8 +43,8 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
43
43
  protected findFreeWorkerNodeKey(): number;
44
44
  /**
45
45
  * Gets the worker task runtime.
46
- * If the required statistics are `avgRunTime`, the average runtime is returned.
47
- * If the required statistics are `medRunTime`, the median runtime is returned.
46
+ * If the task statistics require the average runtime, the average runtime is returned.
47
+ * If the task statistics require the median runtime , the median runtime is returned.
48
48
  *
49
49
  * @param workerNodeKey - The worker node key.
50
50
  * @returns The worker task runtime.
@@ -52,18 +52,27 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
52
52
  protected getWorkerTaskRunTime(workerNodeKey: number): number;
53
53
  /**
54
54
  * Gets the worker task wait time.
55
- * If the required statistics are `avgWaitTime`, the average wait time is returned.
56
- * If the required statistics are `medWaitTime`, the median wait time is returned.
55
+ * If the task statistics require the average wait time, the average wait time is returned.
56
+ * If the task statistics require the median wait time, the median wait time is returned.
57
57
  *
58
58
  * @param workerNodeKey - The worker node key.
59
59
  * @returns The worker task wait time.
60
60
  */
61
- protected getWorkerWaitTime(workerNodeKey: number): number;
61
+ protected getWorkerTaskWaitTime(workerNodeKey: number): number;
62
+ /**
63
+ * Gets the worker task ELU.
64
+ * If the task statistics require the average ELU, the average ELU is returned.
65
+ * If the task statistics require the median ELU, the median ELU is returned.
66
+ *
67
+ * @param workerNodeKey - The worker node key.
68
+ * @returns The worker task ELU.
69
+ */
70
+ protected getWorkerTaskElu(workerNodeKey: number): number;
62
71
  protected computeDefaultWorkerWeight(): number;
63
72
  /**
64
73
  * Finds the first free worker node key based on the number of tasks the worker has applied.
65
74
  *
66
- * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
75
+ * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
67
76
  *
68
77
  * If no free worker is found, `-1` is returned.
69
78
  *
@@ -73,7 +82,7 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
73
82
  /**
74
83
  * Finds the last free worker node key based on the number of tasks the worker has applied.
75
84
  *
76
- * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
85
+ * If a worker is found with `0` executing tasks, it is detected as free and its worker node key is returned.
77
86
  *
78
87
  * If no free worker is found, `-1` is returned.
79
88
  *
@@ -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, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
+ import { type IWorkerChoiceStrategy, type TaskStatisticsRequirements, type 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, TaskStatistics, WorkerChoiceStrategyOptions
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 taskStatistics: TaskStatistics;
15
+ readonly taskStatisticsRequirements: TaskStatisticsRequirements;
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, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
+ import type { IWorkerChoiceStrategy, TaskStatisticsRequirements, WorkerChoiceStrategyOptions } from './selection-strategies-types';
5
5
  /**
6
6
  * Selects the least busy worker.
7
7
  *
@@ -11,7 +11,7 @@ import type { IWorkerChoiceStrategy, TaskStatistics, WorkerChoiceStrategyOptions
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 taskStatistics: TaskStatistics;
14
+ readonly taskStatisticsRequirements: TaskStatisticsRequirements;
15
15
  /** @inheritDoc */
16
16
  constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
17
17
  /** @inheritDoc */
@@ -0,0 +1,25 @@
1
+ import type { IPool } from '../pool';
2
+ import type { IWorker } from '../worker';
3
+ import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
4
+ import type { IWorkerChoiceStrategy, TaskStatisticsRequirements, WorkerChoiceStrategyOptions } from './selection-strategies-types';
5
+ /**
6
+ * Selects the worker with the least ELU.
7
+ *
8
+ * @typeParam Worker - Type of worker which manages the strategy.
9
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
10
+ * @typeParam Response - Type of execution response. This can only be serializable data.
11
+ */
12
+ export declare class LeastEluWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
13
+ /** @inheritDoc */
14
+ readonly taskStatisticsRequirements: TaskStatisticsRequirements;
15
+ /** @inheritDoc */
16
+ constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
17
+ /** @inheritDoc */
18
+ reset(): boolean;
19
+ /** @inheritDoc */
20
+ update(): boolean;
21
+ /** @inheritDoc */
22
+ choose(): number;
23
+ /** @inheritDoc */
24
+ remove(): boolean;
25
+ }
@@ -14,6 +14,12 @@ export declare const WorkerChoiceStrategies: Readonly<{
14
14
  * Least busy worker selection strategy.
15
15
  */
16
16
  readonly LEAST_BUSY: "LEAST_BUSY";
17
+ /**
18
+ * Least ELU worker selection strategy.
19
+ *
20
+ * @experimental
21
+ */
22
+ readonly LEAST_ELU: "LEAST_ELU";
17
23
  /**
18
24
  * Fair share worker selection strategy.
19
25
  */
@@ -33,22 +39,53 @@ export declare const WorkerChoiceStrategies: Readonly<{
33
39
  * Worker choice strategy.
34
40
  */
35
41
  export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies;
42
+ /**
43
+ * Enumeration of measurements.
44
+ */
45
+ export declare const Measurements: Readonly<{
46
+ readonly runTime: "runTime";
47
+ readonly waitTime: "waitTime";
48
+ readonly elu: "elu";
49
+ }>;
50
+ /**
51
+ * Measurement.
52
+ */
53
+ export type Measurement = keyof typeof Measurements;
54
+ /**
55
+ * Measurement options.
56
+ */
57
+ export interface MeasurementOptions {
58
+ /**
59
+ * Set measurement median.
60
+ */
61
+ median: boolean;
62
+ }
36
63
  /**
37
64
  * Worker choice strategy options.
38
65
  */
39
66
  export interface WorkerChoiceStrategyOptions {
40
67
  /**
41
- * Use tasks median runtime instead of average runtime.
68
+ * Measurement to use for worker choice strategy.
69
+ */
70
+ measurement?: Measurement;
71
+ /**
72
+ * Runtime options.
42
73
  *
43
- * @defaultValue false
74
+ * @defaultValue \{ median: false \}
44
75
  */
45
- medRunTime?: boolean;
76
+ runTime?: MeasurementOptions;
46
77
  /**
47
- * Use tasks median wait time instead of average runtime.
78
+ * Wait time options.
48
79
  *
49
- * @defaultValue false
80
+ * @defaultValue \{ median: false \}
50
81
  */
51
- medWaitTime?: boolean;
82
+ waitTime?: MeasurementOptions;
83
+ /**
84
+ * Event loop utilization options.
85
+ *
86
+ * @defaultValue \{ median: false \}
87
+ */
88
+ elu?: MeasurementOptions;
52
89
  /**
53
90
  * Worker weights to use for weighted round robin worker selection strategy.
54
91
  * Weight is the tasks maximum average or median runtime in milliseconds.
@@ -58,48 +95,51 @@ export interface WorkerChoiceStrategyOptions {
58
95
  weights?: Record<number, number>;
59
96
  }
60
97
  /**
61
- * Pool worker tasks usage statistics requirements.
98
+ * Measurement statistics requirements.
62
99
  *
63
100
  * @internal
64
101
  */
65
- export interface TaskStatistics {
102
+ export interface MeasurementStatisticsRequirements {
66
103
  /**
67
- * Require tasks runtime.
104
+ * Require measurement aggregate.
68
105
  */
69
- runTime: boolean;
106
+ aggregate: boolean;
70
107
  /**
71
- * Require tasks average runtime.
108
+ * Require measurement average.
72
109
  */
73
- avgRunTime: boolean;
110
+ average: boolean;
74
111
  /**
75
- * Require tasks median runtime.
112
+ * Require measurement median.
76
113
  */
77
- medRunTime: boolean;
78
- /**
79
- * Require tasks wait time.
80
- */
81
- waitTime: boolean;
114
+ median: boolean;
115
+ }
116
+ /**
117
+ * Pool worker node worker usage statistics requirements.
118
+ *
119
+ * @internal
120
+ */
121
+ export interface TaskStatisticsRequirements {
82
122
  /**
83
- * Require tasks average wait time.
123
+ * Tasks runtime requirements.
84
124
  */
85
- avgWaitTime: boolean;
125
+ runTime: MeasurementStatisticsRequirements;
86
126
  /**
87
- * Require tasks median wait time.
127
+ * Tasks wait time requirements.
88
128
  */
89
- medWaitTime: boolean;
129
+ waitTime: MeasurementStatisticsRequirements;
90
130
  /**
91
- * Event loop utilization.
131
+ * Tasks event loop utilization requirements.
92
132
  */
93
- elu: boolean;
133
+ elu: MeasurementStatisticsRequirements;
94
134
  }
95
135
  /**
96
136
  * Worker choice strategy interface.
97
137
  */
98
138
  export interface IWorkerChoiceStrategy {
99
139
  /**
100
- * Required tasks statistics.
140
+ * Tasks statistics requirements.
101
141
  */
102
- readonly taskStatistics: TaskStatistics;
142
+ readonly taskStatisticsRequirements: TaskStatisticsRequirements;
103
143
  /**
104
144
  * Resets strategy internals.
105
145
  *
@@ -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, TaskStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
+ import type { IWorkerChoiceStrategy, TaskStatisticsRequirements, 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, TaskStatistics, WorkerChoiceStrategyOptions
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 taskStatistics: TaskStatistics;
15
+ readonly taskStatisticsRequirements: TaskStatisticsRequirements;
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 { TaskStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
3
+ import type { TaskStatisticsRequirements, 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 task statistics in the context.
23
+ * Gets the worker choice strategy task statistics requirements in the context.
24
24
  *
25
- * @returns The task statistics.
25
+ * @returns The task statistics requirements.
26
26
  */
27
- getTaskStatistics(): TaskStatistics;
27
+ getTaskStatisticsRequirements(): TaskStatisticsRequirements;
28
28
  /**
29
29
  * Sets the worker choice strategy to use in the context.
30
30
  *
@@ -23,8 +23,6 @@ export type ThreadWorkerWithMessageChannel = Worker & Draft<MessageChannel>;
23
23
  *
24
24
  * It is possible to perform tasks in sync or asynchronous mode as you prefer.
25
25
  *
26
- * This pool selects the threads in a round robin fashion.
27
- *
28
26
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
29
27
  * @typeParam Response - Type of execution response. This can only be serializable data.
30
28
  * @author [Alessandro Pio Ardizio](https://github.com/pioardi)
@@ -1,5 +1,3 @@
1
- /// <reference types="node" />
2
- import type { EventLoopUtilization } from 'node:perf_hooks';
3
1
  import type { CircularArray } from '../circular-array';
4
2
  import type { Queue } from '../queue';
5
3
  /**
@@ -43,59 +41,83 @@ export interface Task<Data = unknown> {
43
41
  readonly id?: string;
44
42
  }
45
43
  /**
46
- * Worker tasks usage statistics.
44
+ * Measurement statistics.
47
45
  *
48
46
  * @internal
49
47
  */
50
- export interface TasksUsage {
48
+ export interface MeasurementStatistics {
51
49
  /**
52
- * Number of tasks executed.
50
+ * Measurement aggregate.
53
51
  */
54
- ran: number;
52
+ aggregate: number;
55
53
  /**
56
- * Number of tasks running.
54
+ * Measurement average.
57
55
  */
58
- running: number;
56
+ average: number;
59
57
  /**
60
- * Tasks runtime.
58
+ * Measurement median.
61
59
  */
62
- runTime: number;
60
+ median: number;
63
61
  /**
64
- * Tasks runtime history.
62
+ * Measurement history.
65
63
  */
66
- runTimeHistory: CircularArray<number>;
64
+ history: CircularArray<number>;
65
+ }
66
+ /**
67
+ * Event loop utilization measurement statistics.
68
+ *
69
+ * @internal
70
+ */
71
+ export interface EventLoopUtilizationMeasurementStatistics {
72
+ idle: MeasurementStatistics;
73
+ active: MeasurementStatistics;
74
+ utilization: number;
75
+ }
76
+ /**
77
+ * Task statistics.
78
+ *
79
+ * @internal
80
+ */
81
+ export interface TaskStatistics {
67
82
  /**
68
- * Average tasks runtime.
83
+ * Number of executed tasks.
69
84
  */
70
- avgRunTime: number;
85
+ executed: number;
71
86
  /**
72
- * Median tasks runtime.
87
+ * Number of executing tasks.
73
88
  */
74
- medRunTime: number;
89
+ executing: number;
75
90
  /**
76
- * Tasks wait time.
91
+ * Number of queued tasks.
77
92
  */
78
- waitTime: number;
93
+ readonly queued: number;
79
94
  /**
80
- * Tasks wait time history.
95
+ * Number of failed tasks.
81
96
  */
82
- waitTimeHistory: CircularArray<number>;
97
+ failed: number;
98
+ }
99
+ /**
100
+ * Worker usage statistics.
101
+ *
102
+ * @internal
103
+ */
104
+ export interface WorkerUsage {
83
105
  /**
84
- * Average tasks wait time.
106
+ * Tasks statistics.
85
107
  */
86
- avgWaitTime: number;
108
+ tasks: TaskStatistics;
87
109
  /**
88
- * Median tasks wait time.
110
+ * Tasks runtime statistics.
89
111
  */
90
- medWaitTime: number;
112
+ runTime: MeasurementStatistics;
91
113
  /**
92
- * Number of tasks errored.
114
+ * Tasks wait time statistics.
93
115
  */
94
- error: number;
116
+ waitTime: MeasurementStatistics;
95
117
  /**
96
- * Event loop utilization.
118
+ * Tasks event loop utilization statistics.
97
119
  */
98
- elu: EventLoopUtilization | undefined;
120
+ elu: EventLoopUtilizationMeasurementStatistics;
99
121
  }
100
122
  /**
101
123
  * Worker interface.
@@ -129,9 +151,9 @@ export interface WorkerNode<Worker extends IWorker, Data = unknown> {
129
151
  */
130
152
  readonly worker: Worker;
131
153
  /**
132
- * Worker node tasks usage statistics.
154
+ * Worker node worker usage statistics.
133
155
  */
134
- tasksUsage: TasksUsage;
156
+ workerUsage: WorkerUsage;
135
157
  /**
136
158
  * Worker node tasks queue.
137
159
  */
@@ -14,22 +14,54 @@ import type { IWorker, Task } from './pools/worker';
14
14
  export type Draft<T> = {
15
15
  -readonly [P in keyof T]?: T[P];
16
16
  };
17
+ /**
18
+ * Task error.
19
+ *
20
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
21
+ */
22
+ export interface TaskError<Data = unknown> {
23
+ /**
24
+ * Error message.
25
+ */
26
+ message: string;
27
+ /**
28
+ * Data passed to the worker triggering the error.
29
+ */
30
+ data?: Data;
31
+ }
32
+ /**
33
+ * Task performance.
34
+ */
35
+ export interface TaskPerformance {
36
+ /**
37
+ * Task performance timestamp.
38
+ */
39
+ timestamp: number;
40
+ /**
41
+ * Task runtime.
42
+ */
43
+ runTime?: number;
44
+ /**
45
+ * Task event loop utilization.
46
+ */
47
+ elu?: EventLoopUtilization;
48
+ }
17
49
  /**
18
50
  * Performance statistics computation.
19
51
  */
20
52
  export interface WorkerStatistics {
21
53
  runTime: boolean;
22
- waitTime: boolean;
23
54
  elu: boolean;
24
55
  }
25
56
  /**
26
57
  * Message object that is passed between main worker and worker.
27
58
  *
59
+ * @typeParam MessageData - Type of data sent to and/or from the worker. This can only be serializable data.
28
60
  * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
29
61
  * @typeParam MainWorker - Type of main worker.
30
62
  * @internal
31
63
  */
32
- export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker | MessagePort = ClusterWorker | MessagePort> extends Task<Data> {
64
+ export interface MessageValue<MessageData = unknown, Data = unknown, MainWorker extends ClusterWorker | MessagePort = ClusterWorker | MessagePort> extends Task<MessageData> {
33
65
  /**
34
66
  * Kill code.
35
67
  */
@@ -37,23 +69,11 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
37
69
  /**
38
70
  * Task error.
39
71
  */
40
- readonly error?: string;
41
- /**
42
- * Task data triggering task error.
43
- */
44
- readonly errorData?: unknown;
45
- /**
46
- * Runtime.
47
- */
48
- readonly runTime?: number;
49
- /**
50
- * Wait time.
51
- */
52
- readonly waitTime?: number;
72
+ readonly taskError?: TaskError<Data>;
53
73
  /**
54
- * Event loop utilization.
74
+ * Task performance.
55
75
  */
56
- readonly elu?: EventLoopUtilization;
76
+ readonly taskPerformance?: TaskPerformance;
57
77
  /**
58
78
  * Reference to main worker.
59
79
  */
@@ -2,23 +2,12 @@
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
- /// <reference types="node" />
6
5
  import { AsyncResource } from 'node:async_hooks';
7
6
  import type { Worker } from 'node:cluster';
8
7
  import type { MessagePort } from 'node:worker_threads';
9
- import { type EventLoopUtilization } from 'node:perf_hooks';
10
8
  import type { MessageValue, WorkerStatistics } from '../utility-types';
11
9
  import { type WorkerOptions } from './worker-options';
12
10
  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
- }
22
11
  /**
23
12
  * Base class that implements some shared logic for all poolifier workers.
24
13
  *
@@ -68,7 +57,7 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
68
57
  *
69
58
  * @param message - Message received.
70
59
  */
71
- protected messageListener(message: MessageValue<Data, MainWorker>): void;
60
+ protected messageListener(message: MessageValue<Data, Data, MainWorker>): void;
72
61
  /**
73
62
  * Returns the main worker.
74
63
  *
@@ -80,7 +69,7 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
80
69
  *
81
70
  * @param message - The response message.
82
71
  */
83
- protected abstract sendToMainWorker(message: MessageValue<Response>): void;
72
+ protected abstract sendToMainWorker(message: MessageValue<Response, Data>): void;
84
73
  /**
85
74
  * Checks if the worker should be terminated, because its living too long.
86
75
  */
@@ -3,11 +3,11 @@
3
3
  */
4
4
  export declare const KillBehaviors: Readonly<{
5
5
  /**
6
- * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **wont** be deleted.
6
+ * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still executing, then the worker **wont** be deleted.
7
7
  */
8
8
  readonly SOFT: "SOFT";
9
9
  /**
10
- * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
10
+ * If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still executing, then the worker will be deleted.
11
11
  */
12
12
  readonly HARD: "HARD";
13
13
  }>;
@@ -51,8 +51,8 @@ export interface WorkerOptions {
51
51
  /**
52
52
  * `killBehavior` dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
53
53
  *
54
- * - SOFT: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted.
55
- * - HARD: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
54
+ * - SOFT: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still executing, then the worker **won't** be deleted.
55
+ * - HARD: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still executing, then the worker will be deleted.
56
56
  *
57
57
  * This option only apply to the newly created workers.
58
58
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.5.4",
3
+ "version": "2.6.0",
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",
@@ -25,7 +25,7 @@
25
25
  "pnpm": ">=8.6.0"
26
26
  },
27
27
  "volta": {
28
- "node": "20.2.0",
28
+ "node": "20.3.0",
29
29
  "pnpm": "8.6.1"
30
30
  },
31
31
  "repository": {