poolifier 2.4.8 → 2.4.10

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
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
- <img src="./images/logo.png" width="340px" height="266px"/>
2
+ <img src="./images/logo.png" width="340px" height="266px"/>
3
3
  </div>
4
4
 
5
5
  <h2 align="center">Node Thread Pool and Cluster Pool :arrow_double_up: :on:</h2>
@@ -32,7 +32,7 @@
32
32
  Poolifier is used to perform CPU intensive and I/O intensive tasks on nodejs servers, it implements worker pools (yes, more worker pool implementations, so you can choose which one fit better for you) using [worker-threads](https://nodejs.org/api/worker_threads.html#worker_threads_worker_threads) and cluster pools using [Node.js cluster](https://nodejs.org/api/cluster.html) modules.
33
33
  With poolifier you can improve your **performance** and resolve problems related to the event loop.
34
34
  Moreover you can execute your tasks using an API designed to improve the **developer experience**.
35
- Please consult our <a href="#general-guidance">general guidelines</a>
35
+ Please consult our [general guidelines](#general-guidance).
36
36
 
37
37
  - Performance :racehorse: [benchmarks](./benchmarks/README.md)
38
38
  - Security :bank: :cop: [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=pioardi_poolifier&metric=security_rating)](https://sonarcloud.io/dashboard?id=pioardi_poolifier) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=pioardi_poolifier&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=pioardi_poolifier)
@@ -81,7 +81,7 @@ Please consult our <a href="#general-guidance">general guidelines</a>
81
81
  Node pool contains two [worker-threads](https://nodejs.org/api/worker_threads.html#worker_threads_worker_threads)/[cluster worker](https://nodejs.org/api/cluster.html#cluster_class_worker) pool implementations, you don't have to deal with worker-threads/cluster worker complexity.
82
82
  The first implementation is a static worker pool, with a defined number of workers that are started at creation time and will be reused.
83
83
  The second implementation is a dynamic worker pool with a number of worker started at creation time (these workers will be always active and reused) and other workers created when the load will increase (with an upper limit, these workers will be reused when active), the new created workers will be stopped after a configurable period of inactivity.
84
- You have to implement your worker extending the ThreadWorker or ClusterWorker class
84
+ You have to implement your worker extending the ThreadWorker or ClusterWorker class.
85
85
 
86
86
  ## Installation
87
87
 
@@ -104,12 +104,11 @@ function yourFunction(data) {
104
104
  }
105
105
 
106
106
  module.exports = new ThreadWorker(yourFunction, {
107
- maxInactiveTime: 60000,
108
- async: false
107
+ maxInactiveTime: 60000
109
108
  })
110
109
  ```
111
110
 
112
- Instantiate your pool based on your needed :
111
+ Instantiate your pool based on your needs :
113
112
 
114
113
  ```js
115
114
  'use strict'
@@ -140,7 +139,7 @@ pool.execute({}).then(res => {
140
139
 
141
140
  You can do the same with the classes ClusterWorker, FixedClusterPool and DynamicClusterPool.
142
141
 
143
- **See examples folder for more details (in particular if you want to use a pool for [multiple functions](./examples/multiFunctionExample.js)).**
142
+ **See examples folder for more details (in particular if you want to use a pool for [multiple functions](./examples/multiFunctionExample.js))**.
144
143
  **Now TypeScript is also supported, find how to use it into the example folder**.
145
144
 
146
145
  Remember that workers can only send and receive serializable data.
@@ -149,9 +148,7 @@ Remember that workers can only send and receive serializable data.
149
148
 
150
149
  Node versions >= 16.x are supported.
151
150
 
152
- ## API
153
-
154
- ### [Documentation](https://poolifier.github.io/poolifier/)
151
+ ## [API](https://poolifier.github.io/poolifier/)
155
152
 
156
153
  ### `pool = new FixedThreadPool/FixedClusterPool(numberOfThreads/numberOfWorkers, filePath, opts)`
157
154
 
@@ -171,7 +168,7 @@ Node versions >= 16.x are supported.
171
168
  - `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN`: Submit tasks to worker using a weighted round robin scheduling algorithm based on tasks execution time
172
169
  - `WorkerChoiceStrategies.FAIR_SHARE`: Submit tasks to worker using a fair share tasks scheduling algorithm based on tasks execution time
173
170
 
174
- `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks
171
+ `WorkerChoiceStrategies.WEIGHTED_ROUND_ROBIN` and `WorkerChoiceStrategies.FAIR_SHARE` strategies are targeted to heavy and long tasks.
175
172
  Default: `WorkerChoiceStrategies.ROUND_ROBIN`
176
173
 
177
174
  - `workerChoiceStrategyOptions` (optional) - The worker choice strategy options object to use in this pool.
@@ -179,17 +176,19 @@ Node versions >= 16.x are supported.
179
176
 
180
177
  - `medRunTime` (optional) - Use the tasks median run time instead of the tasks average run time in worker choice strategies.
181
178
 
182
- Default: { medRunTime: false }
179
+ Default: `{ medRunTime: false }`
183
180
 
184
- - `enableEvents` (optional) - Events emission enablement in this pool. Default: true
185
- - `enableTasksQueue` (optional, experimental) - Tasks queue per worker enablement in this pool. Default: false
181
+ - `enableEvents` (optional) - Events emission enablement in this pool.
182
+ Default: true
183
+ - `enableTasksQueue` (optional) - Tasks queue per worker enablement in this pool.
184
+ Default: false
186
185
 
187
- - `tasksQueueOptions` (optional, experimental) - The worker tasks queue options object to use in this pool.
186
+ - `tasksQueueOptions` (optional) - The worker tasks queue options object to use in this pool.
188
187
  Properties:
189
188
 
190
189
  - `concurrency` (optional) - The maximum number of tasks that can be executed concurrently on a worker.
191
190
 
192
- Default: { concurrency: 1 }
191
+ Default: `{ concurrency: 1 }`
193
192
 
194
193
  ### `pool = new DynamicThreadPool/DynamicClusterPool(min, max, filePath, opts)`
195
194
 
@@ -200,8 +199,8 @@ Node versions >= 16.x are supported.
200
199
 
201
200
  ### `pool.execute(data)`
202
201
 
203
- Execute method is available on both pool implementations (return type: Promise):
204
- `data` (mandatory) An object that you want to pass to your worker implementation
202
+ `data` (optional) An object that you want to pass to your worker implementation
203
+ This method is available on both pool implementations and returns a promise.
205
204
 
206
205
  ### `pool.destroy()`
207
206
 
@@ -213,14 +212,15 @@ This method will call the terminate method on each worker.
213
212
  `fn` (mandatory) The function that you want to execute on the worker
214
213
  `opts` (optional) An object with these properties:
215
214
 
216
- - `maxInactiveTime` - Max time to wait tasks to work on (in ms), after this period the new worker will die.
215
+ - `maxInactiveTime` (optional) - Max time to wait tasks to work on in milliseconds, after this period the new worker will die.
217
216
  The last active time of your worker unit will be updated when a task is submitted to a worker or when a worker terminate a task.
218
217
  If `killBehavior` is set to `KillBehaviors.HARD` this value represents also the timeout for the tasks that you submit to the pool, when this timeout expires your tasks is interrupted and the worker is killed if is not part of the minimum size of the pool.
219
218
  If `killBehavior` is set to `KillBehaviors.SOFT` your tasks have no timeout and your workers will not be terminated until your task is completed.
220
- Default: 60000 ms
219
+ Default: 60000
221
220
 
222
- - `async` - true/false, true if your function contains async code pieces, else false
223
- - `killBehavior` - Dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
221
+ - `async` (optional) - true/false. Set to true if your function contains async code pieces, else false.
222
+ Default: false
223
+ - `killBehavior` (optional) - Dictates if your async unit (worker/process) will be deleted in case that a task is active on it.
224
224
  **KillBehaviors.SOFT**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker **won't** be deleted.
225
225
  **KillBehaviors.HARD**: If `currentTime - lastActiveTime` is greater than `maxInactiveTime` but a task is still running, then the worker will be deleted.
226
226
  This option only apply to the newly created workers.
@@ -238,11 +238,11 @@ Please take a look at [which tasks run on the libuv thread pool](https://nodejs.
238
238
 
239
239
  **If your task runs on libuv thread pool**, you can try to:
240
240
 
241
- - Tune the libuv thread pool size setting the [UV_THREADPOOL_SIZE](https://nodejs.org/api/cli.html#cli_uv_threadpool_size_size)
241
+ - Tune the libuv thread pool size setting the [UV_THREADPOOL_SIZE](https://nodejs.org/api/cli.html#cli_uv_threadpool_size_size).
242
242
 
243
243
  and/or
244
244
 
245
- - Use poolifier cluster pool that spawning child processes will also increase the number of libuv threads since that any new child process comes with a separated libuv thread pool. **More threads does not mean more fast, so please tune your application.**
245
+ - Use poolifier cluster pool that spawning child processes will also increase the number of libuv threads since that any new child process comes with a separated libuv thread pool. **More threads does not mean more fast, so please tune your application**.
246
246
 
247
247
  ### Cluster vs Threads worker pools
248
248
 
@@ -254,7 +254,7 @@ Consider that by default Node.js already has great performance for I/O tasks (as
254
254
  Cluster pools are built on top of Node.js [cluster](https://nodejs.org/api/cluster.html) module.
255
255
 
256
256
  If your task contains code that runs on libuv plus code that is CPU intensive or I/O intensive you either split it either combine more strategies (i.e. tune the number of libuv threads and use cluster/thread pools).
257
- But in general, **always profile your application**
257
+ But in general, **always profile your application**.
258
258
 
259
259
  ### Fixed vs Dynamic pools
260
260
 
@@ -262,13 +262,14 @@ To choose your pool consider that with a FixedThreadPool/FixedClusterPool or a D
262
262
  Increasing the memory footprint, your application will be ready to accept more tasks, but during idle time your application will consume more memory.
263
263
  One good choose from my point of view is to profile your application using Fixed/Dynamic worker pool, and to see your application metrics when you increase/decrease the num of workers.
264
264
  For example you could keep the memory footprint low choosing a DynamicThreadPool/DynamicClusterPool with 5 workers, and allow to create new workers until 50/100 when needed, this is the advantage to use the DynamicThreadPool/DynamicClusterPool.
265
- But in general, **always profile your application**
265
+ But in general, **always profile your application**.
266
266
 
267
267
  ## Contribute
268
268
 
269
- See guidelines [CONTRIBUTING](CONTRIBUTING.md)
270
269
  Choose your task here [2.4.x](https://github.com/orgs/poolifier/projects/1), propose an idea, a fix, an improvement.
271
270
 
271
+ See [CONTRIBUTING](CONTRIBUTING.md) guidelines.
272
+
272
273
  ## Team
273
274
 
274
275
  <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
package/lib/index.d.ts CHANGED
@@ -16,5 +16,5 @@ export { ClusterWorker } from './worker/cluster-worker';
16
16
  export { ThreadWorker } from './worker/thread-worker';
17
17
  export { KillBehaviors } from './worker/worker-options';
18
18
  export type { KillBehavior, WorkerOptions } from './worker/worker-options';
19
- export type { Draft, PromiseResponseWrapper, MessageValue } from './utility-types';
19
+ export type { Draft, PromiseResponseWrapper, MessageValue, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './utility-types';
20
20
  export type { CircularArray } from './circular-array';
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,r=require("node:events"),t=require("node:cluster"),s=require("node:crypto"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class a extends r{}const h=Object.freeze({full:"full",busy:"busy"}),u=Object.freeze((()=>{})),k={medRunTime:!1},c=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const l=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class d{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(r,t=k){this.pool=r,this.opts=t,this.checkOptions(this.opts),this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.medRunTime=!0)}}class p extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),t=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(t??0)})}}class m extends d{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class g extends d{reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class T extends d{nextWorkerNodeId=0;reset(){return this.nextWorkerNodeId=0,!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.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class w extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,r){super(e,r),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,t,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of i.cpus()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/i.cpus().length)}}class f{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=l.ROUND_ROBIN,t=k){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[l.ROUND_ROBIN,new T(e,t)],[l.LESS_USED,new g(e,t)],[l.LESS_BUSY,new m(e,t)],[l.FAIR_SHARE,new p(e,t)],[l.WEIGHTED_ROUND_ROBIN,new w(e,t)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType!==e&&(this.workerChoiceStrategyType=e),this.workerChoiceStrategies.get(this.workerChoiceStrategyType)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class W extends Array{size;constructor(e=1024,...r){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...r)}push(...e){const r=super.push(...e);return r>this.size&&super.splice(0,r-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const r=super.concat(e);return r.size=this.size,r.length>r.size&&r.splice(0,r.length-r.size),r}splice(e,r,...t){let s;return arguments.length>=3&&void 0!==r?(s=super.splice(e,r),this.push(...t)):s=2===arguments.length?super.splice(e,r):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let r=e;r<this.size;r++)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{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!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.bind(this),this.executeTask.bind(this),this.enqueueTask.bind(this),this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new a),this.workerChoiceStrategyContext=new f(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(r){if(null==r)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(r))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(r<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===r)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(this.opts.workerChoiceStrategy=e.workerChoiceStrategy??l.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??k,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue){if(e.tasksQueueOptions?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.tasksQueueOptions.concurrency}'`);this.opts.tasksQueueOptions={concurrency:e.tasksQueueOptions?.concurrency??1}}}checkValidWorkerChoiceStrategy(e){if(!Object.values(l).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,r)=>e+r.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,r)=>e+r.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[r,t]=this.chooseWorkerNode(),i={data:e??{},id:s.randomUUID()},o=new Promise(((e,r)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:r,worker:t.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[r].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(r,i):this.executeTask(r,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,r)=>{this.flushTasksQueue(r),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(t.runTimeHistory.push(r.runTime??0),t.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const r=e.slice().sort(((e,r)=>e-r)),t=Math.floor(r.length/2);return r.length%2==0?r[t/2]:(r[t-1]+r[t])/2})(t.runTimeHistory)))}chooseWorkerNode(){let r;if(this.type===e.DYNAMIC&&!this.full&&this.internalBusy()){const e=this.createAndSetupWorker();this.registerWorkerMessageListener(e,(r=>{var t;t=c.HARD,(r.kill===t||null!=r.kill&&0===this.getWorkerTasksUsage(e)?.running)&&(this.flushTasksQueueByWorker(e),this.destroyWorker(e))})),r=this.getWorkerNodeKey(e)}else r=this.workerChoiceStrategyContext.execute();return[r,this.workerNodes[r]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);if(null!=r){null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterTaskExecutionHook(r.worker,e),this.promiseResponseMap.delete(e.id);const t=this.getWorkerNodeKey(r.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(t)>0&&this.executeTask(t,this.dequeueTask(t))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(h.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(h.full))}setWorkerNodeTasksUsage(e,r){e.tasksUsage=r}getWorkerTasksUsage(e){const r=this.getWorkerNodeKey(e);if(-1!==r)return this.workerNodes[r].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,r,t,s){this.workerNodes[e]={worker:r,tasksUsage:t,tasksQueue:s}}removeWorkerNode(e){const r=this.getWorkerNodeKey(e);this.workerNodes.splice(r,1),this.workerChoiceStrategyContext.remove(r)}executeTask(e,r){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,r)}enqueueTask(e,r){return this.workerNodes[e].tasksQueue.push(r)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const r of this.workerNodes[e].tasksQueue)this.executeTask(e,r)}flushTasksQueueByWorker(e){const r=this.getWorkerNodeKey(e);this.flushTasksQueue(r)}}class N extends y{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class S extends y{constructor(e,r,t={}){super(e,r,t)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:r,port2:t}=new o.MessageChannel;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const R=6e4,x=c.SOFT;class I extends n.AsyncResource{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:x,maxInactiveTime:R}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??R)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){null!=e.data&&null!=e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??x,this.opts.maxInactiveTime=e.maxInactiveTime??R,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??R)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=performance.now(),s=e(r.data),i=performance.now()-t;this.sendToMainWorker({data:s,id:r.id,runTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,r){const t=performance.now();e(r.data).then((e=>{const s=performance.now()-t;return this.sendToMainWorker({data:e,id:r.id,runTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(u)}}exports.ClusterWorker=class extends I{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 N{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends S{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=N,exports.FixedThreadPool=S,exports.KillBehaviors=c,exports.PoolEvents=h,exports.ThreadWorker=class extends I{constructor(e,r={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=l;
1
+ "use strict";var e,t=require("node:events"),r=require("node:cluster"),s=require("node:crypto"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class h extends t{}const a=Object.freeze({full:"full",busy:"busy"}),u=Object.freeze((()=>{})),k={medRunTime:!1},c=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const l=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class d{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(t,r=k){this.pool=t,this.opts=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose=this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime)}setOptions(e){e=e??k,this.checkOptions(e),this.opts=e}}class p extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(r);const s=this.workerLastVirtualTaskTimestamp.get(r)?.end??0;s<t&&(t=s,e=r)}return e}remove(e){const t=this.workerLastVirtualTaskTimestamp.delete(e);for(const[t,r]of this.workerLastVirtualTaskTimestamp.entries())t>e&&this.workerLastVirtualTaskTimestamp.set(t-1,r);return t}computeWorkerLastVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),r=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:t,end:t+(r??0)})}}class m extends d{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(e){return!0}}class T extends d{constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(e){return!0}}class g extends d{nextWorkerNodeId=0;constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!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.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class w extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,t=k){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const t=this.workersTaskRunTime.get(e)?.runTime??0,r=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return t<r?this.setWorkerTaskRunTime(e,r,t+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,r,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const t=this.workersTaskRunTime.delete(e);for(const[t,r]of this.workersTaskRunTime)t>e&&this.workersTaskRunTime.set(t-1,r);return t}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,t,r){this.workersTaskRunTime.set(e,{weight:t,runTime:r})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){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)}}class f{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=l.ROUND_ROBIN,r=k){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[l.ROUND_ROBIN,new(g.bind(this))(e,r)],[l.LESS_USED,new(T.bind(this))(e,r)],[l.LESS_BUSY,new(m.bind(this))(e,r)],[l.FAIR_SHARE,new(p.bind(this))(e,r)],[l.WEIGHTED_ROUND_ROBIN,new(w.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){this.workerChoiceStrategies.forEach((t=>{t.setOptions(e)}))}}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{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new h),this.workerChoiceStrategyContext=new f(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(t){if(null==t)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(t))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(t<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===t)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??l.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??k,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(l).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidTasksQueueOptions(e){if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,r]=this.chooseWorkerNode(),i={data:e??{},id:s.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:r.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[t].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(t,i):this.executeTask(t,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.getWorkerTasksUsage(e);--r.running,++r.run,null!=t.error&&++r.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(r.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==r.run&&(r.avgRunTime=r.runTime/r.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(r.runTimeHistory.push(t.runTime??0),r.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),r=Math.floor(t.length/2);return t.length%2==0?t[r/2]:(t[r-1]+t[r])/2})(r.runTimeHistory)))}chooseWorkerNode(){let t;if(this.type===e.DYNAMIC&&!this.full&&this.internalBusy()){const e=this.createAndSetupWorker();this.registerWorkerMessageListener(e,(t=>{var r;r=c.HARD,(t.kill===r||null!=t.kill&&0===this.getWorkerTasksUsage(e)?.running)&&(this.flushTasksQueueByWorker(e),this.destroyWorker(e))})),t=this.getWorkerNodeKey(e)}else t=this.workerChoiceStrategyContext.execute();return[t,this.workerNodes[t]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):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(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(a.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(a.full))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}getWorkerTasksUsage(e){const t=this.getWorkerNodeKey(e);if(-1!==t)return this.workerNodes[t].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);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.push(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const t of this.workerNodes[e].tasksQueue)this.executeTask(e,t)}flushTasksQueueByWorker(e){const t=this.getWorkerNodeKey(e);this.flushTasksQueue(t)}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class N extends y{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.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 r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class S extends y{constructor(e,t,r={}){super(e,t,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})}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 e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const R=6e4,x=c.SOFT;class I extends n.AsyncResource{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:x,maxInactiveTime:R}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkFunctionInput(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??R)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,r)}))}messageListener(e,t){null!=e.id&&null!=e.data?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.run.bind(this),this,t,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??x,this.opts.maxInactiveTime=e.maxInactiveTime??R,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function");if("AsyncFunction"===e.constructor.name&&!1===this.opts.async)throw new Error("fn parameter is an async function, please set the async option to true")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??R)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const r=performance.now(),s=e(t.data),i=performance.now()-r;this.sendToMainWorker({data:s,id:t.id,runTime:i})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now();e(t.data).then((e=>{const s=performance.now()-r;return this.sendToMainWorker({data:e,id:t.id,runTime:s}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(u)}}exports.ClusterWorker=class extends I{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends N{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends S{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=N,exports.FixedThreadPool=S,exports.KillBehaviors=c,exports.PoolEvents=a,exports.ThreadWorker=class extends I{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=l;
package/lib/index.mjs CHANGED
@@ -1 +1 @@
1
- import e from"node:events";import r from"node:cluster";import t from"node:crypto";import{cpus as s}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as a,parentPort as h}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));class c extends e{}const l=Object.freeze({full:"full",busy:"busy"}),d=Object.freeze((()=>{})),p={medRunTime:!1},m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const g=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class T{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,r=p){this.pool=e,this.opts=r,this.checkOptions(this.opts),this.isDynamicPool=this.pool.type===k.DYNAMIC,this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.medRunTime=!0)}}class w extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,r=1/0;for(const[t]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(t);const s=this.workerLastVirtualTaskTimestamp.get(t)?.end??0;s<r&&(r=s,e=t)}return e}remove(e){const r=this.workerLastVirtualTaskTimestamp.delete(e);for(const[r,t]of this.workerLastVirtualTaskTimestamp.entries())r>e&&this.workerLastVirtualTaskTimestamp.set(r-1,t);return r}computeWorkerLastVirtualTaskTimestamp(e){const r=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),t=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:r,end:r+(t??0)})}}class f extends T{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<t&&(t=i,r=e)}return r}remove(e){return!0}}class W extends T{reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let r,t=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<t&&(t=o,r=e)}return r}remove(e){return!0}}class y extends T{nextWorkerNodeId=0;reset(){return this.nextWorkerNodeId=0,!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.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class N extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,r){super(e,r),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const r=this.workersTaskRunTime.get(e)?.runTime??0,t=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return r<t?this.setWorkerTaskRunTime(e,t,r+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,t,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const r=this.workersTaskRunTime.delete(e);for(const[r,t]of this.workersTaskRunTime)r>e&&this.workersTaskRunTime.set(r-1,t);return r}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,r,t){this.workersTaskRunTime.set(e,{weight:r,runTime:t})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const r of s()){const t=r.speed.toString().length-1;e+=1/(r.speed/Math.pow(10,t))*Math.pow(10,t)}return Math.round(e/s().length)}}class S{workerChoiceStrategyType;workerChoiceStrategies;constructor(e,r=g.ROUND_ROBIN,t=p){this.workerChoiceStrategyType=r,this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new y(e,t)],[g.LESS_USED,new W(e,t)],[g.LESS_BUSY,new f(e,t)],[g.FAIR_SHARE,new w(e,t)],[g.WEIGHTED_ROUND_ROBIN,new N(e,t)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategyType!==e&&(this.workerChoiceStrategyType=e),this.workerChoiceStrategies.get(this.workerChoiceStrategyType)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategyType).remove(e)}}class R extends Array{size;constructor(e=1024,...r){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...r)}push(...e){const r=super.push(...e);return r>this.size&&super.splice(0,r-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const r=super.concat(e);return r.size=this.size,r.length>r.size&&r.splice(0,r.length-r.size),r}splice(e,r,...t){let s;return arguments.length>=3&&void 0!==r?(s=super.splice(e,r),this.push(...t)):s=2===arguments.length?super.splice(e,r):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let r=e;r<this.size;r++)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 I{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,r,t){if(this.numberOfWorkers=e,this.filePath=r,this.opts=t,!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.bind(this),this.executeTask.bind(this),this.enqueueTask.bind(this),this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new c),this.workerChoiceStrategyContext=new S(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(this.opts.workerChoiceStrategy=e.workerChoiceStrategy??g.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??p,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue){if(e.tasksQueueOptions?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.tasksQueueOptions.concurrency}'`);this.opts.tasksQueueOptions={concurrency:e.tasksQueueOptions?.concurrency??1}}}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,r)=>e+r.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,r)=>e+r.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((r=>r.worker===e))}setWorkerChoiceStrategy(e){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(e)}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[r,s]=this.chooseWorkerNode(),i={data:e??{},id:t.randomUUID()},o=new Promise(((e,r)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:r,worker:s.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[r].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(r,i):this.executeTask(r,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,r)=>{this.flushTasksQueue(r),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,r){const t=this.getWorkerTasksUsage(e);--t.running,++t.run,null!=r.error&&++t.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(t.runTime+=r.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==t.run&&(t.avgRunTime=t.runTime/t.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(t.runTimeHistory.push(r.runTime??0),t.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const r=e.slice().sort(((e,r)=>e-r)),t=Math.floor(r.length/2);return r.length%2==0?r[t/2]:(r[t-1]+r[t])/2})(t.runTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const r=this.createAndSetupWorker();this.registerWorkerMessageListener(r,(e=>{var t;t=m.HARD,(e.kill===t||null!=e.kill&&0===this.getWorkerTasksUsage(r)?.running)&&(this.flushTasksQueueByWorker(r),this.destroyWorker(r))})),e=this.getWorkerNodeKey(r)}else e=this.workerChoiceStrategyContext.execute();return[e,this.workerNodes[e]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??d),e.on("error",this.opts.errorHandler??d),e.on("online",this.opts.onlineHandler??d),e.on("exit",this.opts.exitHandler??d),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const r=this.promiseResponseMap.get(e.id);if(null!=r){null!=e.error?r.reject(e.error):r.resolve(e.data),this.afterTaskExecutionHook(r.worker,e),this.promiseResponseMap.delete(e.id);const t=this.getWorkerNodeKey(r.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(t)>0&&this.executeTask(t,this.dequeueTask(t))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(l.busy),this.type===k.DYNAMIC&&this.full&&this.emitter?.emit(l.full))}setWorkerNodeTasksUsage(e,r){e.tasksUsage=r}getWorkerTasksUsage(e){const r=this.getWorkerNodeKey(e);if(-1!==r)return this.workerNodes[r].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,r,t,s){this.workerNodes[e]={worker:r,tasksUsage:t,tasksQueue:s}}removeWorkerNode(e){const r=this.getWorkerNodeKey(e);this.workerNodes.splice(r,1),this.workerChoiceStrategyContext.remove(r)}executeTask(e,r){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,r)}enqueueTask(e,r){return this.workerNodes[e].tasksQueue.push(r)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const r of this.workerNodes[e].tasksQueue)this.executeTask(e,r)}flushTasksQueueByWorker(e){const r=this.getWorkerNodeKey(e);this.flushTasksQueue(r)}}class x extends I{opts;constructor(e,r,t={}){super(e,r,t),this.opts=t}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,r){e.send(r)}registerWorkerMessageListener(e,r){e.on("message",r)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class v extends x{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class C extends I{constructor(e,r,t={}){super(e,r,t)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,r){e.postMessage(r)}registerWorkerMessageListener(e,r){e.port2?.on("message",r)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:r,port2:t}=new a;e.postMessage({parent:r},[r]),e.port1=r,e.port2=t,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class E extends C{max;constructor(e,r,t,s={}){super(e,t,s),this.max=r}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}const b=6e4,O=m.SOFT;class M extends u{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,r,t,s,i={killBehavior:O,maxInactiveTime:b}){super(e),this.isMain=r,this.mainWorker=s,this.opts=i,this.checkFunctionInput(t),this.checkWorkerOptions(this.opts),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??b)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,t)}))}messageListener(e,r){null!=e.data&&null!=e.id?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,r,e):this.runInAsyncScope(this.run.bind(this),this,r,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??O,this.opts.maxInactiveTime=e.maxInactiveTime??b,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??b)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,r){try{const t=performance.now(),s=e(r.data),i=performance.now()-t;this.sendToMainWorker({data:s,id:r.id,runTime:i})}catch(e){const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,r){const t=performance.now();e(r.data).then((e=>{const s=performance.now()-t;return this.sendToMainWorker({data:e,id:r.id,runTime:s}),null})).catch((e=>{const t=this.handleError(e);this.sendToMainWorker({error:t,id:r.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(d)}}class U extends M{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class D extends M{constructor(e,r={}){super("worker-thread-pool:poolifier",i,e,h,r)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{U as ClusterWorker,v as DynamicClusterPool,E as DynamicThreadPool,x as FixedClusterPool,C as FixedThreadPool,m as KillBehaviors,l as PoolEvents,D as ThreadWorker,g as WorkerChoiceStrategies};
1
+ import e from"node:events";import t from"node:cluster";import s from"node:crypto";import{cpus as r}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as h,parentPort as a}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));class c extends e{}const l=Object.freeze({full:"full",busy:"busy"}),d=Object.freeze((()=>{})),p={medRunTime:!1},m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const T=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class g{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){this.pool=e,this.opts=t,this.isDynamicPool=this.pool.type===k.DYNAMIC,this.choose=this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime)}setOptions(e){e=e??p,this.checkOptions(e),this.opts=e}}class w extends g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,t=1/0;for(const[s]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(s);const r=this.workerLastVirtualTaskTimestamp.get(s)?.end??0;r<t&&(t=r,e=s)}return e}remove(e){const t=this.workerLastVirtualTaskTimestamp.delete(e);for(const[t,s]of this.workerLastVirtualTaskTimestamp.entries())t>e&&this.workerLastVirtualTaskTimestamp.set(t-1,s);return t}computeWorkerLastVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),s=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:t,end:t+(s??0)})}}class f extends g{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,s=1/0;for(const[e,r]of this.pool.workerNodes.entries()){const i=r.tasksUsage.runTime;if(0===i)return e;i<s&&(s=i,t=e)}return t}remove(e){return!0}}class W extends g{constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.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.run+i.running;if(0===o)return e;o<s&&(s=o,t=e)}return t}remove(e){return!0}}class y extends g{nextWorkerNodeId=0;constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!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.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class N extends g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,t=p){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const t=this.workersTaskRunTime.get(e)?.runTime??0,s=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return t<s?this.setWorkerTaskRunTime(e,s,t+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,s,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const t=this.workersTaskRunTime.delete(e);for(const[t,s]of this.workersTaskRunTime)t>e&&this.workersTaskRunTime.set(t-1,s);return t}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,t,s){this.workersTaskRunTime.set(e,{weight:t,runTime:s})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const t of r()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/r().length)}}class S{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=T.ROUND_ROBIN,s=p){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[T.ROUND_ROBIN,new(y.bind(this))(e,s)],[T.LESS_USED,new(W.bind(this))(e,s)],[T.LESS_BUSY,new(f.bind(this))(e,s)],[T.FAIR_SHARE,new(w.bind(this))(e,s)],[T.WEIGHTED_ROUND_ROBIN,new(N.bind(this))(e,s)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){this.workerChoiceStrategies.forEach((t=>{t.setOptions(e)}))}}class R 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 I{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),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new c),this.workerChoiceStrategyContext=new S(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??T.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??p,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}'`)}checkValidTasksQueueOptions(e){if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,r]=this.chooseWorkerNode(),i={data:e??{},id:s.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:r.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[t].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(t,i):this.executeTask(t,i),this.checkAndEmitEvents(),o}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.getWorkerTasksUsage(e);--s.running,++s.run,null!=t.error&&++s.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(s.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==s.run&&(s.avgRunTime=s.runTime/s.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(s.runTimeHistory.push(t.runTime??0),s.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),s=Math.floor(t.length/2);return t.length%2==0?t[s/2]:(t[s-1]+t[s])/2})(s.runTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{var s;s=m.HARD,(e.kill===s||null!=e.kill&&0===this.getWorkerTasksUsage(t)?.running)&&(this.flushTasksQueueByWorker(t),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return[e,this.workerNodes[e]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??d),e.on("error",this.opts.errorHandler??d),e.on("online",this.opts.onlineHandler??d),e.on("exit",this.opts.exitHandler??d),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):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(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(l.busy),this.type===k.DYNAMIC&&this.full&&this.emitter?.emit(l.full))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}getWorkerTasksUsage(e){const t=this.getWorkerNodeKey(e);if(-1!==t)return this.workerNodes[t].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,t,s,r){this.workerNodes[e]={worker:t,tasksUsage:s,tasksQueue:r}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);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.push(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const t of this.workerNodes[e].tasksQueue)this.executeTask(e,t)}flushTasksQueueByWorker(e){const t=this.getWorkerNodeKey(e);this.flushTasksQueue(t)}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class x extends I{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 k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class O extends x{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class v extends I{constructor(e,t,s={}){super(e,t,s)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:s}=new h;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class b extends v{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}const C=6e4,E=m.SOFT;class M extends u{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,t,s,r,i={killBehavior:E,maxInactiveTime:C}){super(e),this.isMain=t,this.mainWorker=r,this.opts=i,this.checkWorkerOptions(this.opts),this.checkFunctionInput(s),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??C)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,s)}))}messageListener(e,t){null!=e.id&&null!=e.data?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.run.bind(this),this,t,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??E,this.opts.maxInactiveTime=e.maxInactiveTime??C,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function");if("AsyncFunction"===e.constructor.name&&!1===this.opts.async)throw new Error("fn parameter is an async function, please set the async option to true")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??C)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const s=performance.now(),r=e(t.data),i=performance.now()-s;this.sendToMainWorker({data:r,id:t.id,runTime:i})}catch(e){const s=this.handleError(e);this.sendToMainWorker({error:s,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const s=performance.now();e(t.data).then((e=>{const r=performance.now()-s;return this.sendToMainWorker({data:e,id:t.id,runTime:r}),null})).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({error:s,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(d)}}class Q extends M{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 U extends M{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,a,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{Q as ClusterWorker,O as DynamicClusterPool,b as DynamicThreadPool,x as FixedClusterPool,v as FixedThreadPool,m as KillBehaviors,l as PoolEvents,U as ThreadWorker,T as WorkerChoiceStrategies};
@@ -1,8 +1,8 @@
1
1
  import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
2
- import { type IPool, type PoolOptions, PoolType } from './pool';
2
+ import { type IPool, type PoolOptions, type TasksQueueOptions, PoolType } from './pool';
3
3
  import { PoolEmitter } from './pool';
4
4
  import type { IWorker, WorkerNode } from './worker';
5
- import { type WorkerChoiceStrategy } from './selection-strategies/selection-strategies-types';
5
+ import { type WorkerChoiceStrategy, type WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types';
6
6
  import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context';
7
7
  /**
8
8
  * Base class that implements some shared logic for all poolifier pools.
@@ -38,7 +38,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
38
38
  * Constructs a new poolifier pool.
39
39
  *
40
40
  * @param numberOfWorkers - Number of workers that this pool should manage.
41
- * @param filePath - Path to the worker-file.
41
+ * @param filePath - Path to the worker file.
42
42
  * @param opts - Options for the pool.
43
43
  */
44
44
  constructor(numberOfWorkers: number, filePath: string, opts: PoolOptions<Worker>);
@@ -46,6 +46,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
46
46
  private checkNumberOfWorkers;
47
47
  private checkPoolOptions;
48
48
  private checkValidWorkerChoiceStrategy;
49
+ private checkValidTasksQueueOptions;
49
50
  /** @inheritDoc */
50
51
  abstract get type(): PoolType;
51
52
  /**
@@ -64,7 +65,14 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
64
65
  */
65
66
  private getWorkerNodeKey;
66
67
  /** @inheritDoc */
67
- setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
68
+ setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy, workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions): void;
69
+ /** @inheritDoc */
70
+ setWorkerChoiceStrategyOptions(workerChoiceStrategyOptions: WorkerChoiceStrategyOptions): void;
71
+ /** @inheritDoc */
72
+ enableTasksQueue(enable: boolean, tasksQueueOptions?: TasksQueueOptions): void;
73
+ /** @inheritDoc */
74
+ setTasksQueueOptions(tasksQueueOptions: TasksQueueOptions): void;
75
+ private buildTasksQueueOptions;
68
76
  /**
69
77
  * Whether the pool is full or not.
70
78
  *
@@ -81,7 +89,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
81
89
  /** @inheritDoc */
82
90
  findFreeWorkerNodeKey(): number;
83
91
  /** @inheritDoc */
84
- execute(data: Data): Promise<Response>;
92
+ execute(data?: Data): Promise<Response>;
85
93
  /** @inheritDoc */
86
94
  destroy(): Promise<void>;
87
95
  /**
@@ -174,7 +182,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
174
182
  * Gets the given worker its tasks usage in the pool.
175
183
  *
176
184
  * @param worker - The worker.
177
- * @throws {@link Error} if the worker is not found in the pool worker nodes.
185
+ * @throws Error if the worker is not found in the pool worker nodes.
178
186
  * @returns The worker tasks usage.
179
187
  */
180
188
  private getWorkerTasksUsage;
@@ -206,4 +214,5 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
206
214
  private tasksQueueSize;
207
215
  private flushTasksQueue;
208
216
  private flushTasksQueueByWorker;
217
+ private flushTasksQueues;
209
218
  }
@@ -69,6 +69,8 @@ export interface PoolOptions<Worker extends IWorker> {
69
69
  exitHandler?: ExitHandler<Worker>;
70
70
  /**
71
71
  * The worker choice strategy to use in this pool.
72
+ *
73
+ * @defaultValue WorkerChoiceStrategies.ROUND_ROBIN
72
74
  */
73
75
  workerChoiceStrategy?: WorkerChoiceStrategy;
74
76
  /**
@@ -84,14 +86,11 @@ export interface PoolOptions<Worker extends IWorker> {
84
86
  /**
85
87
  * Pool worker tasks queue.
86
88
  *
87
- * @experimental
88
89
  * @defaultValue false
89
90
  */
90
91
  enableTasksQueue?: boolean;
91
92
  /**
92
93
  * Pool worker tasks queue options.
93
- *
94
- * @experimental
95
94
  */
96
95
  tasksQueueOptions?: TasksQueueOptions;
97
96
  }
@@ -133,12 +132,12 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
133
132
  */
134
133
  findFreeWorkerNodeKey: () => number;
135
134
  /**
136
- * Performs the task specified in the constructor with the data parameter.
135
+ * Executes the function specified in the worker constructor with the task data input parameter.
137
136
  *
138
- * @param data - The input for the specified task. This can only be serializable data.
139
- * @returns Promise that will be resolved when the task is successfully completed.
137
+ * @param data - The task input data for the specified worker function. This can only be serializable data.
138
+ * @returns Promise that will be fulfilled when the task is completed.
140
139
  */
141
- execute: (data: Data) => Promise<Response>;
140
+ execute: (data?: Data) => Promise<Response>;
142
141
  /**
143
142
  * Shutdowns every current worker in this pool.
144
143
  */
@@ -147,6 +146,26 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
147
146
  * Sets the worker choice strategy in this pool.
148
147
  *
149
148
  * @param workerChoiceStrategy - The worker choice strategy.
149
+ * @param workerChoiceStrategyOptions - The worker choice strategy options.
150
+ */
151
+ setWorkerChoiceStrategy: (workerChoiceStrategy: WorkerChoiceStrategy, workerChoiceStrategyOptions?: WorkerChoiceStrategyOptions) => void;
152
+ /**
153
+ * Sets the worker choice strategy options in this pool.
154
+ *
155
+ * @param workerChoiceStrategyOptions - The worker choice strategy options.
156
+ */
157
+ setWorkerChoiceStrategyOptions: (workerChoiceStrategyOptions: WorkerChoiceStrategyOptions) => void;
158
+ /**
159
+ * Enables/disables the worker tasks queue in this pool.
160
+ *
161
+ * @param enable - Whether to enable or disable the worker tasks queue.
162
+ * @param tasksQueueOptions - The worker tasks queue options.
163
+ */
164
+ enableTasksQueue: (enable: boolean, tasksQueueOptions?: TasksQueueOptions) => void;
165
+ /**
166
+ * Sets the worker tasks queue options in this pool.
167
+ *
168
+ * @param tasksQueueOptions - The worker tasks queue options.
150
169
  */
151
- setWorkerChoiceStrategy: (workerChoiceStrategy: WorkerChoiceStrategy) => void;
170
+ setTasksQueueOptions: (tasksQueueOptions: TasksQueueOptions) => void;
152
171
  }
@@ -10,7 +10,7 @@ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOpt
10
10
  */
11
11
  export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> implements IWorkerChoiceStrategy {
12
12
  protected readonly pool: IPool<Worker, Data, Response>;
13
- protected readonly opts: WorkerChoiceStrategyOptions;
13
+ protected opts: WorkerChoiceStrategyOptions;
14
14
  /** @inheritDoc */
15
15
  protected readonly isDynamicPool: boolean;
16
16
  /** @inheritDoc */
@@ -22,11 +22,13 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
22
22
  * @param opts - The worker choice strategy options.
23
23
  */
24
24
  constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
25
- private checkOptions;
25
+ protected checkOptions(opts: WorkerChoiceStrategyOptions): void;
26
26
  /** @inheritDoc */
27
27
  abstract reset(): boolean;
28
28
  /** @inheritDoc */
29
29
  abstract choose(): number;
30
30
  /** @inheritDoc */
31
31
  abstract remove(workerNodeKey: number): boolean;
32
+ /** @inheritDoc */
33
+ setOptions(opts: WorkerChoiceStrategyOptions): void;
32
34
  }
@@ -1,6 +1,7 @@
1
+ import type { IPool } from '../pool';
1
2
  import type { IWorker } from '../worker';
2
3
  import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
- import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
4
+ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
5
  /**
5
6
  * Selects the next worker with a fair share scheduling algorithm.
6
7
  * Loosely modeled after the fair queueing algorithm: https://en.wikipedia.org/wiki/Fair_queuing.
@@ -17,6 +18,8 @@ export declare class FairShareWorkerChoiceStrategy<Worker extends IWorker, Data
17
18
  */
18
19
  private readonly workerLastVirtualTaskTimestamp;
19
20
  /** @inheritDoc */
21
+ constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
22
+ /** @inheritDoc */
20
23
  reset(): boolean;
21
24
  /** @inheritDoc */
22
25
  choose(): number;
@@ -1,6 +1,7 @@
1
+ import type { IPool } from '../pool';
1
2
  import type { IWorker } from '../worker';
2
3
  import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
- import type { IWorkerChoiceStrategy, RequiredStatistics } from './selection-strategies-types';
4
+ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
5
  /**
5
6
  * Selects the less busy worker.
6
7
  *
@@ -12,6 +13,8 @@ export declare class LessBusyWorkerChoiceStrategy<Worker extends IWorker, Data =
12
13
  /** @inheritDoc */
13
14
  readonly requiredStatistics: RequiredStatistics;
14
15
  /** @inheritDoc */
16
+ constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
17
+ /** @inheritDoc */
15
18
  reset(): boolean;
16
19
  /** @inheritDoc */
17
20
  choose(): number;
@@ -1,6 +1,7 @@
1
+ import type { IPool } from '../pool';
1
2
  import type { IWorker } from '../worker';
2
3
  import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
- import type { IWorkerChoiceStrategy } from './selection-strategies-types';
4
+ import type { IWorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
5
  /**
5
6
  * Selects the less used worker.
6
7
  *
@@ -9,6 +10,8 @@ import type { IWorkerChoiceStrategy } from './selection-strategies-types';
9
10
  * @typeParam Response - Type of execution response. This can only be serializable data.
10
11
  */
11
12
  export declare class LessUsedWorkerChoiceStrategy<Worker extends IWorker, Data = unknown, Response = unknown> extends AbstractWorkerChoiceStrategy<Worker, Data, Response> implements IWorkerChoiceStrategy {
13
+ /** @inheritDoc */
14
+ constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
12
15
  /** @inheritDoc */
13
16
  reset(): boolean;
14
17
  /** @inheritDoc */
@@ -1,6 +1,7 @@
1
+ import type { IPool } from '../pool';
1
2
  import type { IWorker } from '../worker';
2
3
  import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
- import type { IWorkerChoiceStrategy } from './selection-strategies-types';
4
+ import type { IWorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
5
  /**
5
6
  * Selects the next worker in a round robin fashion.
6
7
  *
@@ -14,6 +15,8 @@ export declare class RoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data
14
15
  */
15
16
  private nextWorkerNodeId;
16
17
  /** @inheritDoc */
18
+ constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
19
+ /** @inheritDoc */
17
20
  reset(): boolean;
18
21
  /** @inheritDoc */
19
22
  choose(): number;
@@ -33,6 +33,8 @@ export type WorkerChoiceStrategy = keyof typeof WorkerChoiceStrategies;
33
33
  export interface WorkerChoiceStrategyOptions {
34
34
  /**
35
35
  * Use tasks median run time instead of average run time.
36
+ *
37
+ * @defaultValue false
36
38
  */
37
39
  medRunTime?: boolean;
38
40
  }
@@ -77,4 +79,10 @@ export interface IWorkerChoiceStrategy {
77
79
  * @param workerNodeKey - The worker node key.
78
80
  */
79
81
  remove: (workerNodeKey: number) => boolean;
82
+ /**
83
+ * Sets the worker choice strategy options.
84
+ *
85
+ * @param opts - The worker choice strategy options.
86
+ */
87
+ setOptions: (opts: WorkerChoiceStrategyOptions) => void;
80
88
  }
@@ -25,12 +25,7 @@ export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IWork
25
25
  * Workers' virtual task runtime.
26
26
  */
27
27
  private readonly workersTaskRunTime;
28
- /**
29
- * Constructs a worker choice strategy that selects with a weighted round robin scheduling algorithm.
30
- *
31
- * @param pool - The pool instance.
32
- * @param opts - The worker choice strategy options.
33
- */
28
+ /** @inheritDoc */
34
29
  constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
35
30
  /** @inheritDoc */
36
31
  reset(): boolean;
@@ -9,16 +9,16 @@ import type { RequiredStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOpti
9
9
  * @typeParam Response - Type of execution response. This can only be serializable data.
10
10
  */
11
11
  export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data = unknown, Response = unknown> {
12
- private workerChoiceStrategyType;
12
+ private workerChoiceStrategy;
13
13
  private readonly workerChoiceStrategies;
14
14
  /**
15
15
  * Worker choice strategy context constructor.
16
16
  *
17
17
  * @param pool - The pool instance.
18
- * @param workerChoiceStrategyType - The worker choice strategy.
18
+ * @param workerChoiceStrategy - The worker choice strategy.
19
19
  * @param opts - The worker choice strategy options.
20
20
  */
21
- constructor(pool: IPool<Worker, Data, Response>, workerChoiceStrategyType?: WorkerChoiceStrategy, opts?: WorkerChoiceStrategyOptions);
21
+ constructor(pool: IPool<Worker, Data, Response>, workerChoiceStrategy?: WorkerChoiceStrategy, opts?: WorkerChoiceStrategyOptions);
22
22
  /**
23
23
  * Gets the worker choice strategy in the context required statistics.
24
24
  *
@@ -44,4 +44,10 @@ export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data =
44
44
  * @returns `true` if the removal is successful, `false` otherwise.
45
45
  */
46
46
  remove(workerNodeKey: number): boolean;
47
+ /**
48
+ * Sets the worker choice strategies in the context options.
49
+ *
50
+ * @param opts - The worker choice strategy options.
51
+ */
52
+ setOptions(opts: WorkerChoiceStrategyOptions): void;
47
53
  }
@@ -36,7 +36,7 @@ export declare class FixedThreadPool<Data = unknown, Response = unknown> extends
36
36
  /** @inheritDoc */
37
37
  protected sendToWorker(worker: ThreadWorkerWithMessageChannel, message: MessageValue<Data>): void;
38
38
  /** @inheritDoc */
39
- protected registerWorkerMessageListener<Message extends Data | Response>(messageChannel: ThreadWorkerWithMessageChannel, listener: (message: MessageValue<Message>) => void): void;
39
+ protected registerWorkerMessageListener<Message extends Data | Response>(worker: ThreadWorkerWithMessageChannel, listener: (message: MessageValue<Message>) => void): void;
40
40
  /** @inheritDoc */
41
41
  protected createWorker(): ThreadWorkerWithMessageChannel;
42
42
  /** @inheritDoc */
@@ -23,7 +23,7 @@ export type ExitHandler<Worker extends IWorker> = (this: Worker, code: number) =
23
23
  */
24
24
  export interface Task<Data = unknown> {
25
25
  /**
26
- * Input data that will be passed to the worker.
26
+ * Task input data that will be passed to the worker.
27
27
  */
28
28
  readonly data?: Data;
29
29
  /**
@@ -74,7 +74,7 @@ export interface IWorker {
74
74
  * Register an event listener.
75
75
  *
76
76
  * @param event - The event.
77
- * @param handler - The event listener.
77
+ * @param handler - The event handler.
78
78
  */
79
79
  on: ((event: 'message', handler: MessageHandler<this>) => void) & ((event: 'error', handler: ErrorHandler<this>) => void) & ((event: 'online', handler: OnlineHandler<this>) => void) & ((event: 'exit', handler: ExitHandler<this>) => void);
80
80
  /**
@@ -6,6 +6,8 @@ import type { KillBehavior } from './worker/worker-options';
6
6
  import type { IWorker, Task } from './pools/worker';
7
7
  /**
8
8
  * Make all properties in T non-readonly.
9
+ *
10
+ * @typeParam T - Type in which properties will be non-readonly.
9
11
  */
10
12
  export type Draft<T> = {
11
13
  -readonly [P in keyof T]?: T[P];
@@ -32,12 +34,32 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
32
34
  readonly runTime?: number;
33
35
  /**
34
36
  * Reference to main worker.
35
- *
36
- * Only for internal use.
37
- * @internal
38
37
  */
39
38
  readonly parent?: MainWorker;
40
39
  }
40
+ /**
41
+ * Worker synchronous function that can be executed.
42
+ *
43
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
44
+ * @typeParam Response - Type of execution response. This can only be serializable data.
45
+ */
46
+ export type WorkerSyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Response;
47
+ /**
48
+ * Worker asynchronous function that can be executed.
49
+ * This function must return a promise.
50
+ *
51
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
52
+ * @typeParam Response - Type of execution response. This can only be serializable data.
53
+ */
54
+ export type WorkerAsyncFunction<Data = unknown, Response = unknown> = (data?: Data) => Promise<Response>;
55
+ /**
56
+ * Worker function that can be executed.
57
+ * This function can be synchronous or asynchronous.
58
+ *
59
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
60
+ * @typeParam Response - Type of execution response. This can only be serializable data.
61
+ */
62
+ export type WorkerFunction<Data = unknown, Response = unknown> = WorkerSyncFunction<Data, Response> | WorkerAsyncFunction<Data, Response>;
41
63
  /**
42
64
  * An object holding the execution response promise resolve/reject callbacks.
43
65
  *
@@ -5,7 +5,7 @@
5
5
  import { AsyncResource } from 'node:async_hooks';
6
6
  import type { Worker } from 'node:cluster';
7
7
  import type { MessagePort } from 'node:worker_threads';
8
- import type { MessageValue } from '../utility-types';
8
+ import type { MessageValue, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from '../utility-types';
9
9
  import type { WorkerOptions } from './worker-options';
10
10
  /**
11
11
  * Base class that implements some shared logic for all poolifier workers.
@@ -35,14 +35,14 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
35
35
  * @param mainWorker - Reference to main worker.
36
36
  * @param opts - Options for the worker.
37
37
  */
38
- constructor(type: string, isMain: boolean, fn: (data: Data) => Response, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
38
+ constructor(type: string, isMain: boolean, fn: WorkerFunction<Data, Response>, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
39
39
  /**
40
40
  * Worker message listener.
41
41
  *
42
42
  * @param message - Message received.
43
43
  * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
44
44
  */
45
- protected messageListener(message: MessageValue<Data, MainWorker>, fn: (data: Data) => Response): void;
45
+ protected messageListener(message: MessageValue<Data, MainWorker>, fn: WorkerFunction<Data, Response>): void;
46
46
  private checkWorkerOptions;
47
47
  /**
48
48
  * Checks if the `fn` parameter is passed to the constructor.
@@ -79,12 +79,12 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
79
79
  * @param fn - Function that will be executed.
80
80
  * @param message - Input data for the given function.
81
81
  */
82
- protected run(fn: (data?: Data) => Response, message: MessageValue<Data>): void;
82
+ protected run(fn: WorkerSyncFunction<Data, Response>, message: MessageValue<Data>): void;
83
83
  /**
84
84
  * Runs the given function asynchronously.
85
85
  *
86
86
  * @param fn - Function that will be executed.
87
87
  * @param message - Input data for the given function.
88
88
  */
89
- protected runAsync(fn: (data?: Data) => Promise<Response>, message: MessageValue<Data>): void;
89
+ protected runAsync(fn: WorkerAsyncFunction<Data, Response>, message: MessageValue<Data>): void;
90
90
  }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import type { Worker } from 'node:cluster';
3
- import type { MessageValue } from '../utility-types';
3
+ import type { MessageValue, WorkerFunction } from '../utility-types';
4
4
  import { AbstractWorker } from './abstract-worker';
5
5
  import type { WorkerOptions } from './worker-options';
6
6
  /**
@@ -24,7 +24,7 @@ export declare class ClusterWorker<Data = unknown, Response = unknown> extends A
24
24
  * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
25
25
  * @param opts - Options for the worker.
26
26
  */
27
- constructor(fn: (data: Data) => Response, opts?: WorkerOptions);
27
+ constructor(fn: WorkerFunction<Data, Response>, opts?: WorkerOptions);
28
28
  /** @inheritDoc */
29
29
  protected sendToMainWorker(message: MessageValue<Response>): void;
30
30
  /** @inheritDoc */
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import type { MessagePort } from 'node:worker_threads';
3
- import type { MessageValue } from '../utility-types';
3
+ import type { MessageValue, WorkerFunction } from '../utility-types';
4
4
  import { AbstractWorker } from './abstract-worker';
5
5
  import type { WorkerOptions } from './worker-options';
6
6
  /**
@@ -24,7 +24,7 @@ export declare class ThreadWorker<Data = unknown, Response = unknown> extends Ab
24
24
  * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
25
25
  * @param opts - Options for the worker.
26
26
  */
27
- constructor(fn: (data: Data) => Response, opts?: WorkerOptions);
27
+ constructor(fn: WorkerFunction<Data, Response>, opts?: WorkerOptions);
28
28
  /** @inheritDoc */
29
29
  protected sendToMainWorker(message: MessageValue<Response>): void;
30
30
  }
@@ -38,7 +38,7 @@ export interface WorkerOptions {
38
38
  * when this timeout expires your tasks is interrupted and the worker is killed if is not part of the minimum size of the pool.
39
39
  * - If `killBehavior` is set to `KillBehaviors.SOFT` your tasks have no timeout and your workers will not be terminated until your task is completed.
40
40
  *
41
- * @defaultValue 60000 ms
41
+ * @defaultValue 60000
42
42
  */
43
43
  maxInactiveTime?: number;
44
44
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.4.8",
3
+ "version": "2.4.10",
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",
@@ -73,8 +73,8 @@
73
73
  "lib"
74
74
  ],
75
75
  "devDependencies": {
76
- "@commitlint/cli": "^17.5.1",
77
- "@commitlint/config-conventional": "^17.4.4",
76
+ "@commitlint/cli": "^17.6.1",
77
+ "@commitlint/config-conventional": "^17.6.1",
78
78
  "@release-it/bumper": "^4.0.2",
79
79
  "@release-it/keep-a-changelog": "^3.1.0",
80
80
  "@rollup/plugin-terser": "^0.4.1",
@@ -87,7 +87,7 @@
87
87
  "eslint": "^8.38.0",
88
88
  "eslint-config-standard": "^17.0.0",
89
89
  "eslint-config-standard-with-typescript": "^34.0.1",
90
- "eslint-define-config": "^1.17.0",
90
+ "eslint-define-config": "^1.18.0",
91
91
  "eslint-import-resolver-typescript": "^3.5.5",
92
92
  "eslint-plugin-import": "^2.27.5",
93
93
  "eslint-plugin-jsdoc": "^41.1.1",