poolifier 2.4.9 → 2.4.11

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>
@@ -11,7 +11,7 @@
11
11
  <img alt="Actions Status" src="https://github.com/poolifier/poolifier/workflows/NodeCI/badge.svg"></a>
12
12
  <a href="https://sonarcloud.io/dashboard?id=pioardi_poolifier">
13
13
  <img alt="Quality Gate Status" src="https://sonarcloud.io/api/project_badges/measure?project=pioardi_poolifier&metric=alert_status"></a>
14
- <a href="https://sonarcloud.io/component_measures/metric/coverage/list?id=pioardi_poolifier">
14
+ <a href="https://sonarcloud.io/dashboard?id=pioardi_poolifier">
15
15
  <img alt="Code Coverage" src="https://sonarcloud.io/api/project_badges/measure?project=pioardi_poolifier&metric=coverage"></a>
16
16
  <a href="https://standardjs.com">
17
17
  <img alt="Javascript Standard Style Guide" src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"></a>
@@ -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 -->
@@ -4,9 +4,13 @@
4
4
  export declare class CircularArray<T> extends Array<T> {
5
5
  size: number;
6
6
  constructor(size?: number, ...items: T[]);
7
+ /** @inheritDoc */
7
8
  push(...items: T[]): number;
9
+ /** @inheritDoc */
8
10
  unshift(...items: T[]): number;
11
+ /** @inheritDoc */
9
12
  concat(...items: Array<T | ConcatArray<T>>): CircularArray<T>;
13
+ /** @inheritDoc */
10
14
  splice(start: number, deleteCount?: number, ...items: T[]): T[];
11
15
  resize(size: number): void;
12
16
  empty(): boolean;
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,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 a extends t{}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(t,r=k){this.pool=t,this.opts=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,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.bind(this),this.workerChoiceStrategies=new Map([[l.ROUND_ROBIN,new g(e,r)],[l.LESS_USED,new T(e,r)],[l.LESS_BUSY,new m(e,r)],[l.FAIR_SHARE,new p(e,r)],[l.WEIGHTED_ROUND_ROBIN,new w(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.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(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){if(!0===this.opts.enableTasksQueue&&!e)for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e);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(h.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(h.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)}}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=h,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;
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 a extends t{}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;toggleFindLastFreeWorkerNodeKey=!1;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}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage?.running)return e;return-1}}class 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)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.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 g extends d{constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(e){return!0}}class T 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(T.bind(this))(e,r)],[l.LESS_USED,new(g.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 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(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.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(h.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(h.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)}))}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")}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())}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=h,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 t from"node:cluster";import r from"node:crypto";import{cpus as s}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as a,parentPort as h}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";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,t=p){this.pool=e,this.opts=t,this.isDynamicPool=this.pool.type===k.DYNAMIC,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 T{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[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 f extends T{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,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 W extends T{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,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 y extends T{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 T{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,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 s()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s().length)}}class S{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=g.ROUND_ROBIN,r=p){this.workerChoiceStrategy=t,this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new y(e,r)],[g.LESS_USED,new W(e,r)],[g.LESS_BUSY,new f(e,r)],[g.FAIR_SHARE,new w(e,r)],[g.WEIGHTED_ROUND_ROBIN,new N(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 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,...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 I{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.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){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&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}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){if(!0===this.opts.enableTasksQueue&&!e)for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e);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,s]=this.chooseWorkerNode(),i={data:e??{},id:r.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:s.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 e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{var r;r=m.HARD,(e.kill===r||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 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(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,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)}}class O extends I{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class x extends O{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class v extends I{constructor(e,t,r={}){super(e,t,r)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:r}=new a;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class C extends v{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}const b=6e4,E=m.SOFT;class M extends u{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:E,maxInactiveTime:b}){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??b)/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??E,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");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??b)&&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(d)}}class Q extends M{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class U extends M{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,h,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{Q as ClusterWorker,x as DynamicClusterPool,C as DynamicThreadPool,O as FixedClusterPool,v as FixedThreadPool,m as KillBehaviors,l as PoolEvents,U as ThreadWorker,g as WorkerChoiceStrategies};
1
+ import e from"node:events";import t from"node:cluster";import r from"node:crypto";import{cpus as s}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as 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 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;toggleFindLastFreeWorkerNodeKey=!1;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}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage?.running)return e;return-1}}class w extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};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[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)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 f extends T{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.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 W extends T{constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(e){return!0}}class y extends T{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 T{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,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 s()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s().length)}}class S{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=g.ROUND_ROBIN,r=p){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new(y.bind(this))(e,r)],[g.LESS_USED,new(W.bind(this))(e,r)],[g.LESS_BUSY,new(f.bind(this))(e,r)],[g.FAIR_SHARE,new(w.bind(this))(e,r)],[g.WEIGHTED_ROUND_ROBIN,new(N.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 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,...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 I{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 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??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&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}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.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,s]=this.chooseWorkerNode(),i={data:e??{},id:r.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:s.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 e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{var r;r=m.HARD,(e.kill===r||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 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(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,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 x extends I{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class O extends x{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class v extends I{constructor(e,t,r={}){super(e,t,r)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:r}=new h;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,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,r,s={}){super(e,r,s),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,r,s,i={killBehavior:E,maxInactiveTime:C}){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??C)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,r)}))}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")}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())}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 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(d)}}class Q extends M{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class 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,g as WorkerChoiceStrategies};
@@ -1,6 +1,5 @@
1
1
  import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
2
- import { type IPool, type PoolOptions, type TasksQueueOptions, PoolType } from './pool';
3
- import { PoolEmitter } from './pool';
2
+ import { PoolEmitter, type IPool, type PoolOptions, type TasksQueueOptions, PoolType } from './pool';
4
3
  import type { IWorker, WorkerNode } from './worker';
5
4
  import { type WorkerChoiceStrategy, type WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types';
6
5
  import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context';
@@ -87,9 +86,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
87
86
  protected abstract get busy(): boolean;
88
87
  protected internalBusy(): boolean;
89
88
  /** @inheritDoc */
90
- findFreeWorkerNodeKey(): number;
91
- /** @inheritDoc */
92
- execute(data: Data): Promise<Response>;
89
+ execute(data?: Data): Promise<Response>;
93
90
  /** @inheritDoc */
94
91
  destroy(): Promise<void>;
95
92
  /**
@@ -214,4 +211,5 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
214
211
  private tasksQueueSize;
215
212
  private flushTasksQueue;
216
213
  private flushTasksQueueByWorker;
214
+ private flushTasksQueues;
217
215
  }
@@ -122,22 +122,12 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
122
122
  */
123
123
  readonly emitter?: PoolEmitter;
124
124
  /**
125
- * Finds a free worker node key based on the number of tasks the worker has applied.
125
+ * Executes the function specified in the worker constructor with the task data input parameter.
126
126
  *
127
- * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
128
- *
129
- * If no free worker is found, `-1` is returned.
130
- *
131
- * @returns A worker node key if there is one, `-1` otherwise.
132
- */
133
- findFreeWorkerNodeKey: () => number;
134
- /**
135
- * Executes the function specified in the constructor with the task data input parameter.
136
- *
137
- * @param data - The task input data for the specified function. This can only be serializable data.
138
- * @returns Promise that will be resolved when the task is successfully completed.
127
+ * @param data - The task input data for the specified worker function. This can only be serializable data.
128
+ * @returns Promise that will be fulfilled when the task is completed.
139
129
  */
140
- execute: (data: Data) => Promise<Response>;
130
+ execute: (data?: Data) => Promise<Response>;
141
131
  /**
142
132
  * Shutdowns every current worker in this pool.
143
133
  */
@@ -11,6 +11,10 @@ import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOpt
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
13
  protected opts: WorkerChoiceStrategyOptions;
14
+ /**
15
+ * Toggles finding the last free worker node key.
16
+ */
17
+ private toggleFindLastFreeWorkerNodeKey;
14
18
  /** @inheritDoc */
15
19
  protected readonly isDynamicPool: boolean;
16
20
  /** @inheritDoc */
@@ -31,4 +35,30 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
31
35
  abstract remove(workerNodeKey: number): boolean;
32
36
  /** @inheritDoc */
33
37
  setOptions(opts: WorkerChoiceStrategyOptions): void;
38
+ /**
39
+ * Finds a free worker node key.
40
+ *
41
+ * @returns The free worker node key or `-1` if there is no free worker node.
42
+ */
43
+ protected findFreeWorkerNodeKey(): number;
44
+ /**
45
+ * Finds the first free worker node key based on the number of tasks the worker has applied.
46
+ *
47
+ * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
48
+ *
49
+ * If no free worker is found, `-1` is returned.
50
+ *
51
+ * @returns A worker node key if there is one, `-1` otherwise.
52
+ */
53
+ private findFirstFreeWorkerNodeKey;
54
+ /**
55
+ * Finds the last free worker node key based on the number of tasks the worker has applied.
56
+ *
57
+ * If a worker is found with `0` running tasks, it is detected as free and its worker node key is returned.
58
+ *
59
+ * If no free worker is found, `-1` is returned.
60
+ *
61
+ * @returns A worker node key if there is one, `-1` otherwise.
62
+ */
63
+ private findLastFreeWorkerNodeKey;
34
64
  }
@@ -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 */
@@ -74,11 +74,11 @@ 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
  /**
81
- * Register a listener to the exit event that will only performed once.
81
+ * Register a listener to the exit event that will only be performed once.
82
82
  *
83
83
  * @param event - `'exit'`.
84
84
  * @param handler - The exit handler.
@@ -37,6 +37,29 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
37
37
  */
38
38
  readonly parent?: MainWorker;
39
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>;
40
63
  /**
41
64
  * An object holding the execution response promise resolve/reject callbacks.
42
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,7 @@ 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 | Promise<Response>, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
39
- /**
40
- * Worker message listener.
41
- *
42
- * @param message - Message received.
43
- * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
44
- */
45
- protected messageListener(message: MessageValue<Data, MainWorker>, fn: (data: Data) => Response | Promise<Response>): void;
38
+ constructor(type: string, isMain: boolean, fn: WorkerFunction<Data, Response>, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
46
39
  private checkWorkerOptions;
47
40
  /**
48
41
  * Checks if the `fn` parameter is passed to the constructor.
@@ -50,6 +43,13 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
50
43
  * @param fn - The function that should be defined.
51
44
  */
52
45
  private checkFunctionInput;
46
+ /**
47
+ * Worker message listener.
48
+ *
49
+ * @param message - Message received.
50
+ * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
51
+ */
52
+ protected messageListener(message: MessageValue<Data, MainWorker>, fn: WorkerFunction<Data, Response>): void;
53
53
  /**
54
54
  * Returns the main worker.
55
55
  *
@@ -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 | Promise<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 | Promise<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.9",
3
+ "version": "2.4.11",
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",
@@ -21,7 +21,11 @@
21
21
  ]
22
22
  },
23
23
  "engines": {
24
- "node": ">=16.0.0"
24
+ "node": ">=16.0.0",
25
+ "pnpm": ">= 8.0.0"
26
+ },
27
+ "volta": {
28
+ "node": "20.0.0"
25
29
  },
26
30
  "repository": {
27
31
  "type": "git",
@@ -79,18 +83,18 @@
79
83
  "@release-it/keep-a-changelog": "^3.1.0",
80
84
  "@rollup/plugin-terser": "^0.4.1",
81
85
  "@rollup/plugin-typescript": "^11.1.0",
82
- "@types/node": "^18.15.11",
83
- "@typescript-eslint/eslint-plugin": "^5.58.0",
84
- "@typescript-eslint/parser": "^5.58.0",
86
+ "@types/node": "^18.16.0",
87
+ "@typescript-eslint/eslint-plugin": "^5.59.0",
88
+ "@typescript-eslint/parser": "^5.59.0",
85
89
  "benny": "^3.7.1",
86
90
  "c8": "^7.13.0",
87
- "eslint": "^8.38.0",
91
+ "eslint": "^8.39.0",
88
92
  "eslint-config-standard": "^17.0.0",
89
93
  "eslint-config-standard-with-typescript": "^34.0.1",
90
94
  "eslint-define-config": "^1.18.0",
91
95
  "eslint-import-resolver-typescript": "^3.5.5",
92
96
  "eslint-plugin-import": "^2.27.5",
93
- "eslint-plugin-jsdoc": "^41.1.1",
97
+ "eslint-plugin-jsdoc": "^43.0.7",
94
98
  "eslint-plugin-n": "^15.7.0",
95
99
  "eslint-plugin-promise": "^6.1.1",
96
100
  "eslint-plugin-spellcheck": "^0.0.20",
@@ -101,29 +105,29 @@
101
105
  "microtime": "^3.1.1",
102
106
  "mocha": "^10.2.0",
103
107
  "mochawesome": "^7.1.3",
104
- "prettier": "^2.8.7",
108
+ "prettier": "^2.8.8",
105
109
  "prettier-plugin-organize-imports": "^3.2.2",
106
110
  "release-it": "^15.10.1",
107
- "rollup": "^3.20.2",
111
+ "rollup": "^3.20.7",
108
112
  "rollup-plugin-analyzer": "^4.0.0",
109
113
  "rollup-plugin-command": "^1.1.3",
110
114
  "rollup-plugin-delete": "^2.0.0",
111
- "sinon": "^15.0.3",
115
+ "sinon": "^15.0.4",
112
116
  "source-map-support": "^0.5.21",
113
117
  "ts-standard": "^12.0.2",
114
- "typedoc": "^0.24.1",
118
+ "typedoc": "^0.24.5",
115
119
  "typescript": "^5.0.4"
116
120
  },
117
121
  "scripts": {
118
- "preinstall": "npx only-allow pnpm",
122
+ "preinstall": "npx --yes only-allow pnpm",
119
123
  "build": "rollup --config --environment BUILD:development",
120
124
  "build:typedoc": "rollup --config --environment BUILD:development --environment DOCUMENTATION",
121
125
  "build:prod": "rollup --config",
122
- "benchmark": "pnpm run build && node -r source-map-support/register benchmarks/internal/bench.js",
123
- "benchmark:debug": "pnpm run build && node -r source-map-support/register --inspect benchmarks/internal/bench.js",
124
- "benchmark:prod": "pnpm run build:prod && node -r source-map-support/register benchmarks/internal/bench.js",
125
- "test": "pnpm run build && c8 mocha 'tests/**/*.test.js'",
126
- "test:debug": "pnpm run build && mocha --no-parallel --inspect 'tests/**/*.test.js'",
126
+ "benchmark": "pnpm build && node -r source-map-support/register benchmarks/internal/bench.js",
127
+ "benchmark:debug": "pnpm build && node -r source-map-support/register --inspect benchmarks/internal/bench.js",
128
+ "benchmark:prod": "pnpm build:prod && node -r source-map-support/register benchmarks/internal/bench.js",
129
+ "test": "pnpm build && c8 mocha 'tests/**/*.test.js'",
130
+ "test:debug": "pnpm build && mocha --no-parallel --inspect 'tests/**/*.test.js'",
127
131
  "coverage": "c8 report --reporter=lcov",
128
132
  "coverage:html": "c8 report --reporter=html",
129
133
  "format": "prettier . --cache --write; ts-standard . --fix",