poolifier 2.4.10 → 2.4.12

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
@@ -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>
@@ -29,7 +29,7 @@
29
29
 
30
30
  ## Why Poolifier?
31
31
 
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.
32
+ Poolifier is used to perform CPU intensive and I/O intensive tasks on nodejs servers, it implements worker pools 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
35
  Please consult our [general guidelines](#general-guidance).
@@ -139,8 +139,7 @@ pool.execute({}).then(res => {
139
139
 
140
140
  You can do the same with the classes ClusterWorker, FixedClusterPool and DynamicClusterPool.
141
141
 
142
- **See examples folder for more details (in particular if you want to use a pool for [multiple functions](./examples/multiFunctionExample.js))**.
143
- **Now TypeScript is also supported, find how to use it into the example folder**.
142
+ **See examples folder for more details (in particular if you want to use a pool with [multiple worker functions](./examples/multiFunctionExample.js))**.
144
143
 
145
144
  Remember that workers can only send and receive serializable data.
146
145
 
@@ -175,6 +174,7 @@ Node versions >= 16.x are supported.
175
174
  Properties:
176
175
 
177
176
  - `medRunTime` (optional) - Use the tasks median run time instead of the tasks average run time in worker choice strategies.
177
+ - `weights` (optional) - The worker weights to use in the weighted round robin worker choice strategy: `{ 0: 200, 1: 300, ..., n: 100 }`
178
178
 
179
179
  Default: `{ medRunTime: false }`
180
180
 
@@ -209,7 +209,7 @@ This method will call the terminate method on each worker.
209
209
 
210
210
  ### `class YourWorker extends ThreadWorker/ClusterWorker`
211
211
 
212
- `fn` (mandatory) The function that you want to execute on the worker
212
+ `taskFunctions` (mandatory) The task function(s) that you want to execute on the worker
213
213
  `opts` (optional) An object with these properties:
214
214
 
215
215
  - `maxInactiveTime` (optional) - Max time to wait tasks to work on in milliseconds, after this period the new worker will die.
@@ -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
@@ -3,7 +3,7 @@ export { FixedClusterPool } from './pools/cluster/fixed';
3
3
  export type { ClusterPoolOptions } from './pools/cluster/fixed';
4
4
  export type { AbstractPool } from './pools/abstract-pool';
5
5
  export { PoolEvents } from './pools/pool';
6
- export type { IPool, PoolEmitter, PoolOptions, PoolEvent, PoolType, TasksQueueOptions } from './pools/pool';
6
+ export type { IPool, PoolEmitter, PoolEvent, PoolOptions, PoolType, TasksQueueOptions } from './pools/pool';
7
7
  export type { ErrorHandler, ExitHandler, IWorker, MessageHandler, OnlineHandler, Task, TasksUsage, WorkerNode } from './pools/worker';
8
8
  export { WorkerChoiceStrategies } from './pools/selection-strategies/selection-strategies-types';
9
9
  export type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './pools/selection-strategies/selection-strategies-types';
@@ -16,5 +16,6 @@ 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, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './utility-types';
19
+ export type { Draft, MessageValue, PromiseResponseWrapper, TaskFunctions, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from './utility-types';
20
20
  export type { CircularArray } from './circular-array';
21
+ export type { Queue } from './queue';
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 h extends t{}const a=Object.freeze({full:"full",busy:"busy"}),u=Object.freeze((()=>{})),k={medRunTime:!1},c=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const l=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class d{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(t,r=k){this.pool=t,this.opts=r,this.isDynamicPool=this.pool.type===e.DYNAMIC,this.choose=this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime)}setOptions(e){e=e??k,this.checkOptions(e),this.opts=e}}class p extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(r);const s=this.workerLastVirtualTaskTimestamp.get(r)?.end??0;s<t&&(t=s,e=r)}return e}remove(e){const t=this.workerLastVirtualTaskTimestamp.delete(e);for(const[t,r]of this.workerLastVirtualTaskTimestamp.entries())t>e&&this.workerLastVirtualTaskTimestamp.set(t-1,r);return t}computeWorkerLastVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),r=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:t,end:t+(r??0)})}}class m extends d{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(e){return!0}}class T extends d{constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(e){return!0}}class g extends d{nextWorkerNodeId=0;constructor(e,t=k){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class w extends d{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,t=k){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const t=this.workersTaskRunTime.get(e)?.runTime??0,r=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return t<r?this.setWorkerTaskRunTime(e,r,t+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,r,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const t=this.workersTaskRunTime.delete(e);for(const[t,r]of this.workersTaskRunTime)t>e&&this.workersTaskRunTime.set(t-1,r);return t}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,t,r){this.workersTaskRunTime.set(e,{weight:t,runTime:r})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const t of i.cpus()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/i.cpus().length)}}class f{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=l.ROUND_ROBIN,r=k){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[l.ROUND_ROBIN,new(g.bind(this))(e,r)],[l.LESS_USED,new(T.bind(this))(e,r)],[l.LESS_BUSY,new(m.bind(this))(e,r)],[l.FAIR_SHARE,new(p.bind(this))(e,r)],[l.WEIGHTED_ROUND_ROBIN,new(w.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){this.workerChoiceStrategies.forEach((t=>{t.setOptions(e)}))}}class W extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class y{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new h),this.workerChoiceStrategyContext=new f(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(t){if(null==t)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(t))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(t<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===e.FIXED&&0===t)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??l.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??k,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(l).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidTasksQueueOptions(e){if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,r]=this.chooseWorkerNode(),i={data:e??{},id:s.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:r.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[t].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(t,i):this.executeTask(t,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.getWorkerTasksUsage(e);--r.running,++r.run,null!=t.error&&++r.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(r.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==r.run&&(r.avgRunTime=r.runTime/r.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(r.runTimeHistory.push(t.runTime??0),r.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),r=Math.floor(t.length/2);return t.length%2==0?t[r/2]:(t[r-1]+t[r])/2})(r.runTimeHistory)))}chooseWorkerNode(){let t;if(this.type===e.DYNAMIC&&!this.full&&this.internalBusy()){const e=this.createAndSetupWorker();this.registerWorkerMessageListener(e,(t=>{var r;r=c.HARD,(t.kill===r||null!=t.kill&&0===this.getWorkerTasksUsage(e)?.running)&&(this.flushTasksQueueByWorker(e),this.destroyWorker(e))})),t=this.getWorkerNodeKey(e)}else t=this.workerChoiceStrategyContext.execute();return[t,this.workerNodes[t]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(a.busy),this.type===e.DYNAMIC&&this.full&&this.emitter?.emit(a.full))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}getWorkerTasksUsage(e){const t=this.getWorkerNodeKey(e);if(-1!==t)return this.workerNodes[t].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new W,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t)}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.push(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const t of this.workerNodes[e].tasksQueue)this.executeTask(e,t)}flushTasksQueueByWorker(e){const t=this.getWorkerNodeKey(e);this.flushTasksQueue(t)}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class N extends y{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){r.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return r.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return r.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class S extends y{constructor(e,t,r={}){super(e,t,r)}isMain(){return o.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o.Worker(this.filePath,{env:o.SHARE_ENV})}afterWorkerSetup(e){const{port1:t,port2:r}=new o.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return e.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const R=6e4,x=c.SOFT;class I extends n.AsyncResource{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:x,maxInactiveTime:R}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkFunctionInput(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??R)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,r)}))}messageListener(e,t){null!=e.id&&null!=e.data?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.run.bind(this),this,t,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??x,this.opts.maxInactiveTime=e.maxInactiveTime??R,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function");if("AsyncFunction"===e.constructor.name&&!1===this.opts.async)throw new Error("fn parameter is an async function, please set the async option to true")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??R)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const r=performance.now(),s=e(t.data),i=performance.now()-r;this.sendToMainWorker({data:s,id:t.id,runTime:i})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now();e(t.data).then((e=>{const s=performance.now()-r;return this.sendToMainWorker({data:e,id:t.id,runTime:s}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(u)}}exports.ClusterWorker=class extends I{constructor(e,t={}){super("worker-cluster-pool:poolifier",r.isPrimary,e,r.worker,t)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends N{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends S{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return e.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=N,exports.FixedThreadPool=S,exports.KillBehaviors=c,exports.PoolEvents=a,exports.ThreadWorker=class extends I{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=l;
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((()=>{})),c={medRunTime:!1},k=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class d extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class p{items;head;tail;constructor(){this.items={},this.head=0,this.tail=0}get size(){return this.tail-this.head}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}}const m=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class g{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,t=c){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}checkOptions(e){if(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),null!=e.weights&&Object.keys(e.weights).length<this.pool.size)throw new Error("Worker choice strategy options must have a weight for each worker node.")}setOptions(e){e=e??c,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 g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workersVirtualTaskTimestamp=[];constructor(e,t=c){super(e,t),this.checkOptions(this.opts)}reset(){return this.workersVirtualTaskTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){const s=this.workersVirtualTaskTimestamp[r]?.end??0;s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskTimestamp.splice(e,1),!0}computeWorkerVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workersVirtualTaskTimestamp[e]?.end??-1/0),r=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workersVirtualTaskTimestamp[e]={start:t,end:t+r}}}class T extends g{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=c){super(e,t),this.checkOptions(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class f extends g{constructor(e,t=c){super(e,t),this.checkOptions(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class y extends g{nextWorkerNodeId=0;constructor(e,t=c){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class W extends g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=c){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerVirtualTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId,this.workerVirtualTaskRunTime=0),!0}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/i.cpus().length)}}class N{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=m.ROUND_ROBIN,r=c){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[m.ROUND_ROBIN,new(y.bind(this))(e,r)],[m.LESS_USED,new(f.bind(this))(e,r)],[m.LESS_BUSY,new(T.bind(this))(e,r)],[m.FAIR_SHARE,new(w.bind(this))(e,r)],[m.WEIGHTED_ROUND_ROBIN,new(W.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){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 S{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 N(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 safe 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){if(!k(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??m.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??c,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(m).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!k(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object")}checkValidTasksQueueOptions(e){if(null!=e&&!k(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get 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.size),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 d,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const[r,i]=this.chooseWorkerNode(),o={name:t,data:e??{},id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:i.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[r].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(r,o):this.executeTask(r,o),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.getWorkerNodeKey(e),s=this.workerNodes[r].tasksUsage;--s.running,++s.run,null!=t.error&&++s.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(s.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==s.run&&(s.avgRunTime=s.runTime/s.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(s.runTimeHistory.push(t.runTime??0),s.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),r=Math.floor(t.length/2);return t.length%2==0?t[r]:(t[r-1]+t[r])/2})(s.runTimeHistory))),this.workerChoiceStrategyContext.update(r)}chooseWorkerNode(){let t;if(this.type===e.DYNAMIC&&!this.full&&this.internalBusy()){const e=this.createAndSetupWorker();this.registerWorkerMessageListener(e,(t=>{const r=this.getWorkerNodeKey(e);var s;s=l.HARD,(t.kill===s||null!=t.kill&&0===this.workerNodes[r].tasksUsage.running)&&(this.flushTasksQueue(r),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}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new d,avgRunTime:0,medRunTime:0,error:0},tasksQueue:new p})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t)}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class b extends S{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 size(){return this.numberOfWorkers}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class x extends S{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 size(){return this.numberOfWorkers}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}const O="default",R=6e4,v=l.SOFT;class C extends n.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:v,maxInactiveTime:R}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??R)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??v,this.opts.maxInactiveTime=e.maxInactiveTime??R,this.opts.async=e.async??!1}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(O,e.bind(this));else{if(!k(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[r,s]of Object.entries(e)){if("function"!=typeof s)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(r,s.bind(this)),t&&(this.taskFunctions.set(O,s.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??R)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(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)}getTaskFunction(e){e=e??O;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function "${e}" not found`);return t}}exports.ClusterWorker=class extends C{constructor(e,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 b{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return e.DYNAMIC}get size(){return this.max}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends x{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 size(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=b,exports.FixedThreadPool=x,exports.KillBehaviors=l,exports.PoolEvents=h,exports.ThreadWorker=class extends C{constructor(e,t={}){super("worker-thread-pool:poolifier",o.isMainThread,e,o.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=m;
package/lib/index.mjs CHANGED
@@ -1 +1 @@
1
- import e from"node:events";import t from"node:cluster";import s from"node:crypto";import{cpus as r}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as h,parentPort as a}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));class c extends e{}const l=Object.freeze({full:"full",busy:"busy"}),d=Object.freeze((()=>{})),p={medRunTime:!1},m=Object.freeze({SOFT:"SOFT",HARD:"HARD"});const T=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LESS_USED:"LESS_USED",LESS_BUSY:"LESS_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN"});class g{pool;opts;isDynamicPool;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){this.pool=e,this.opts=t,this.isDynamicPool=this.pool.type===k.DYNAMIC,this.choose=this.choose.bind(this)}checkOptions(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime)}setOptions(e){e=e??p,this.checkOptions(e),this.opts=e}}class w extends g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workerLastVirtualTaskTimestamp=new Map;constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.workerLastVirtualTaskTimestamp.clear(),!0}choose(){let e,t=1/0;for(const[s]of this.pool.workerNodes.entries()){this.computeWorkerLastVirtualTaskTimestamp(s);const r=this.workerLastVirtualTaskTimestamp.get(s)?.end??0;r<t&&(t=r,e=s)}return e}remove(e){const t=this.workerLastVirtualTaskTimestamp.delete(e);for(const[t,s]of this.workerLastVirtualTaskTimestamp.entries())t>e&&this.workerLastVirtualTaskTimestamp.set(t-1,s);return t}computeWorkerLastVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workerLastVirtualTaskTimestamp.get(e)?.end??-1/0),s=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workerLastVirtualTaskTimestamp.set(e,{start:t,end:t+(s??0)})}}class f extends g{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,s=1/0;for(const[e,r]of this.pool.workerNodes.entries()){const i=r.tasksUsage.runTime;if(0===i)return e;i<s&&(s=i,t=e)}return t}remove(e){return!0}}class W extends g{constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}choose(){const e=this.pool.findFreeWorkerNodeKey();if(-1!==e)return e;let t,s=1/0;for(const[e,r]of this.pool.workerNodes.entries()){const i=r.tasksUsage,o=i.run+i.running;if(0===o)return e;o<s&&(s=o,t=e)}return t}remove(e){return!0}}class y extends g{nextWorkerNodeId=0;constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class N extends g{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workersTaskRunTime=new Map;constructor(e,t=p){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeWorkerWeight(),this.initWorkersTaskRunTime()}reset(){return this.currentWorkerNodeId=0,this.workersTaskRunTime.clear(),this.initWorkersTaskRunTime(),!0}choose(){const e=this.currentWorkerNodeId;this.isDynamicPool&&!this.workersTaskRunTime.has(e)&&this.initWorkerTaskRunTime(e);const t=this.workersTaskRunTime.get(e)?.runTime??0,s=this.workersTaskRunTime.get(e)?.weight??this.defaultWorkerWeight;return t<s?this.setWorkerTaskRunTime(e,s,t+(this.getWorkerVirtualTaskRunTime(e)??0)):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.setWorkerTaskRunTime(this.currentWorkerNodeId,s,0)),e}remove(e){this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId);const t=this.workersTaskRunTime.delete(e);for(const[t,s]of this.workersTaskRunTime)t>e&&this.workersTaskRunTime.set(t-1,s);return t}initWorkersTaskRunTime(){for(const[e]of this.pool.workerNodes.entries())this.initWorkerTaskRunTime(e)}initWorkerTaskRunTime(e){this.setWorkerTaskRunTime(e,this.defaultWorkerWeight,0)}setWorkerTaskRunTime(e,t,s){this.workersTaskRunTime.set(e,{weight:t,runTime:s})}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeWorkerWeight(){let e=0;for(const t of r()){const s=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,s))*Math.pow(10,s)}return Math.round(e/r().length)}}class S{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=T.ROUND_ROBIN,s=p){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[T.ROUND_ROBIN,new(y.bind(this))(e,s)],[T.LESS_USED,new(W.bind(this))(e,s)],[T.LESS_BUSY,new(f.bind(this))(e,s)],[T.FAIR_SHARE,new(w.bind(this))(e,s)],[T.WEIGHTED_ROUND_ROBIN,new(N.bind(this))(e,s)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}execute(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose()}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){this.workerChoiceStrategies.forEach((t=>{t.setOptions(e)}))}}class R extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...s){let r;return arguments.length>=3&&void 0!==t?(r=super.splice(e,t),this.push(...s)):r=2===arguments.length?super.splice(e,t):super.splice(e),r}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class I{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,s){if(this.numberOfWorkers=e,this.filePath=t,this.opts=s,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new c),this.workerChoiceStrategyContext=new S(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){this.opts.workerChoiceStrategy=e.workerChoiceStrategy??T.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??p,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(T).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidTasksQueueOptions(e){if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get numberOfRunningTasks(){return this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0)}get numberOfQueuedTasks(){return!1===this.opts.enableTasksQueue?0:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.length),0)}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.findFreeWorkerNodeKey()}findFreeWorkerNodeKey(){return this.workerNodes.findIndex((e=>0===e.tasksUsage?.running))}async execute(e){const[t,r]=this.chooseWorkerNode(),i={data:e??{},id:s.randomUUID()},o=new Promise(((e,t)=>{this.promiseResponseMap.set(i.id,{resolve:e,reject:t,worker:r.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[t].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(t,i):this.executeTask(t,i),this.checkAndEmitEvents(),o}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const s=this.getWorkerTasksUsage(e);--s.running,++s.run,null!=t.error&&++s.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(s.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==s.run&&(s.avgRunTime=s.runTime/s.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(s.runTimeHistory.push(t.runTime??0),s.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),s=Math.floor(t.length/2);return t.length%2==0?t[s/2]:(t[s-1]+t[s])/2})(s.runTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{var s;s=m.HARD,(e.kill===s||null!=e.kill&&0===this.getWorkerTasksUsage(t)?.running)&&(this.flushTasksQueueByWorker(t),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return[e,this.workerNodes[e]]}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??d),e.on("error",this.opts.errorHandler??d),e.on("online",this.opts.onlineHandler??d),e.on("exit",this.opts.exitHandler??d),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const s=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(s)>0&&this.executeTask(s,this.dequeueTask(s))}}}}checkAndEmitEvents(){!0===this.opts.enableEvents&&(this.busy&&this.emitter?.emit(l.busy),this.type===k.DYNAMIC&&this.full&&this.emitter?.emit(l.full))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}getWorkerTasksUsage(e){const t=this.getWorkerNodeKey(e);if(-1!==t)return this.workerNodes[t].tasksUsage;throw new Error("Worker could not be found in the pool worker nodes")}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new R,avgRunTime:0,medRunTime:0,error:0},tasksQueue:[]})}setWorkerNode(e,t,s,r){this.workerNodes[e]={worker:t,tasksUsage:s,tasksQueue:r}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t)}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.push(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.shift()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.length}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(const t of this.workerNodes[e].tasksQueue)this.executeTask(e,t)}flushTasksQueueByWorker(e){const t=this.getWorkerNodeKey(e);this.flushTasksQueue(t)}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class x extends I{opts;constructor(e,t,s={}){super(e,t,s),this.opts=s}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class O extends x{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class v extends I{constructor(e,t,s={}){super(e,t,s)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:s}=new h;e.postMessage({parent:t},[t]),e.port1=t,e.port2=s,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class b extends v{max;constructor(e,t,s,r={}){super(e,s,r),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}const C=6e4,E=m.SOFT;class M extends u{isMain;mainWorker;opts;lastTaskTimestamp;aliveInterval;constructor(e,t,s,r,i={killBehavior:E,maxInactiveTime:C}){super(e),this.isMain=t,this.mainWorker=r,this.opts=i,this.checkWorkerOptions(this.opts),this.checkFunctionInput(s),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??C)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",(e=>{this.messageListener(e,s)}))}messageListener(e,t){null!=e.id&&null!=e.data?!0===this.opts.async?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.run.bind(this),this,t,e):null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??E,this.opts.maxInactiveTime=e.maxInactiveTime??C,this.opts.async=e.async??!1}checkFunctionInput(e){if(null==e)throw new Error("fn parameter is mandatory");if("function"!=typeof e)throw new TypeError("fn parameter is not a function");if("AsyncFunction"===e.constructor.name&&!1===this.opts.async)throw new Error("fn parameter is an async function, please set the async option to true")}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??C)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}run(e,t){try{const s=performance.now(),r=e(t.data),i=performance.now()-s;this.sendToMainWorker({data:r,id:t.id,runTime:i})}catch(e){const s=this.handleError(e);this.sendToMainWorker({error:s,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const s=performance.now();e(t.data).then((e=>{const r=performance.now()-s;return this.sendToMainWorker({data:e,id:t.id,runTime:r}),null})).catch((e=>{const s=this.handleError(e);this.sendToMainWorker({error:s,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(d)}}class Q extends M{constructor(e,s={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,s)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class U extends M{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,a,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{Q as ClusterWorker,O as DynamicClusterPool,b as DynamicThreadPool,x as FixedClusterPool,v as FixedThreadPool,m as KillBehaviors,l as PoolEvents,U as ThreadWorker,T as WorkerChoiceStrategies};
1
+ 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 c;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(c||(c={}));class k extends e{}const l=Object.freeze({full:"full",busy:"busy"}),d=Object.freeze((()=>{})),p={medRunTime:!1},m=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),g=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class w extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class T{items;head;tail;constructor(){this.items={},this.head=0,this.tail=0}get size(){return this.tail-this.head}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}}const f=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 y{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}checkOptions(e){if(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),null!=e.weights&&Object.keys(e.weights).length<this.pool.size)throw new Error("Worker choice strategy options must have a weight for each worker node.")}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 y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};workersVirtualTaskTimestamp=[];constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.workersVirtualTaskTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){const s=this.workersVirtualTaskTimestamp[r]?.end??0;s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskTimestamp.splice(e,1),!0}computeWorkerVirtualTaskTimestamp(e){const t=Math.max(performance.now(),this.workersVirtualTaskTimestamp[e]?.end??-1/0),r=this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime;this.workersVirtualTaskTimestamp[e]={start:t,end:t+r}}}class N extends y{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1};constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class S extends y{constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class b extends y{nextWorkerNodeId=0;constructor(e,t=p){super(e,t),this.checkOptions(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId=this.nextWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.nextWorkerNodeId),!0}}class O extends y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=p){super(e,t),this.checkOptions(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerVirtualTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId=this.currentWorkerNodeId>this.pool.workerNodes.length-1?this.pool.workerNodes.length-1:this.currentWorkerNodeId,this.workerVirtualTaskRunTime=0),!0}getWorkerVirtualTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}computeDefaultWorkerWeight(){let e=0;for(const t of s()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s().length)}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=f.ROUND_ROBIN,r=p){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[f.ROUND_ROBIN,new(b.bind(this))(e,r)],[f.LESS_USED,new(S.bind(this))(e,r)],[f.LESS_BUSY,new(N.bind(this))(e,r)],[f.FAIR_SHARE,new(W.bind(this))(e,r)],[f.WEIGHTED_ROUND_ROBIN,new(O.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){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 x{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new k),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===c.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!m(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??f.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(f).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!m(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object")}checkValidTasksQueueOptions(e){if(null!=e&&!m(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get 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.size),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.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const[s,i]=this.chooseWorkerNode(),o={name:t,data:e??{},id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:i.worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[s].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(s,o):this.executeTask(s,o),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.getWorkerNodeKey(e),s=this.workerNodes[r].tasksUsage;--s.running,++s.run,null!=t.error&&++s.error,this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(s.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==s.run&&(s.avgRunTime=s.runTime/s.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&(s.runTimeHistory.push(t.runTime??0),s.medRunTime=(e=>{if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t)),r=Math.floor(t.length/2);return t.length%2==0?t[r]:(t[r-1]+t[r])/2})(s.runTimeHistory))),this.workerChoiceStrategyContext.update(r)}chooseWorkerNode(){let e;if(this.type===c.DYNAMIC&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const r=this.getWorkerNodeKey(t);var s;s=g.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].tasksUsage.running)&&(this.flushTasksQueue(r),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return[e,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===c.DYNAMIC&&this.full&&this.emitter?.emit(l.full))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new w,avgRunTime:0,medRunTime:0,error:0},tasksQueue:new T})}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.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class v extends x{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return c.FIXED}get size(){return this.numberOfWorkers}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class I extends v{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return c.DYNAMIC}get size(){return this.max}get full(){return this.workerNodes.length===this.max}get busy(){return this.full&&this.internalBusy()}}class C extends x{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 c.FIXED}get size(){return this.numberOfWorkers}get full(){return this.workerNodes.length===this.numberOfWorkers}get busy(){return this.internalBusy()}}class E extends C{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return c.DYNAMIC}get full(){return this.workerNodes.length===this.max}get size(){return this.max}get busy(){return this.full&&this.internalBusy()}}const F="default",M=6e4,Q=g.SOFT;class z extends u{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:Q,maxInactiveTime:M}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??M)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??Q,this.opts.maxInactiveTime=e.maxInactiveTime??M,this.opts.async=e.async??!1}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(F,e.bind(this));else{if(!m(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[r,s]of Object.entries(e)){if("function"!=typeof s)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(r,s.bind(this)),t&&(this.taskFunctions.set(F,s.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??M)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(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)}getTaskFunction(e){e=e??F;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function "${e}" not found`);return t}}class U extends z{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 A extends z{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,a,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{U as ClusterWorker,I as DynamicClusterPool,E as DynamicThreadPool,v as FixedClusterPool,C as FixedThreadPool,g as KillBehaviors,l as PoolEvents,A as ThreadWorker,f 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 { type IPool, PoolEmitter, type PoolOptions, PoolType, type TasksQueueOptions } 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';
@@ -46,9 +45,12 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
46
45
  private checkNumberOfWorkers;
47
46
  private checkPoolOptions;
48
47
  private checkValidWorkerChoiceStrategy;
48
+ private checkValidWorkerChoiceStrategyOptions;
49
49
  private checkValidTasksQueueOptions;
50
50
  /** @inheritDoc */
51
51
  abstract get type(): PoolType;
52
+ /** @inheritDoc */
53
+ abstract get size(): number;
52
54
  /**
53
55
  * Number of tasks running in the pool.
54
56
  */
@@ -87,9 +89,7 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
87
89
  protected abstract get busy(): boolean;
88
90
  protected internalBusy(): boolean;
89
91
  /** @inheritDoc */
90
- findFreeWorkerNodeKey(): number;
91
- /** @inheritDoc */
92
- execute(data?: Data): Promise<Response>;
92
+ execute(data?: Data, name?: string): Promise<Response>;
93
93
  /** @inheritDoc */
94
94
  destroy(): Promise<void>;
95
95
  /**
@@ -178,14 +178,6 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
178
178
  * @param tasksUsage - The worker node tasks usage.
179
179
  */
180
180
  private setWorkerNodeTasksUsage;
181
- /**
182
- * Gets the given worker its tasks usage in the pool.
183
- *
184
- * @param worker - The worker.
185
- * @throws Error if the worker is not found in the pool worker nodes.
186
- * @returns The worker tasks usage.
187
- */
188
- private getWorkerTasksUsage;
189
181
  /**
190
182
  * Pushes the given worker in the pool worker nodes.
191
183
  *
@@ -213,6 +205,5 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
213
205
  private dequeueTask;
214
206
  private tasksQueueSize;
215
207
  private flushTasksQueue;
216
- private flushTasksQueueByWorker;
217
208
  private flushTasksQueues;
218
209
  }
@@ -26,6 +26,8 @@ export declare class DynamicClusterPool<Data = unknown, Response = unknown> exte
26
26
  /** @inheritDoc */
27
27
  get type(): PoolType;
28
28
  /** @inheritDoc */
29
+ get size(): number;
30
+ /** @inheritDoc */
29
31
  protected get full(): boolean;
30
32
  /** @inheritDoc */
31
33
  protected get busy(): boolean;
@@ -60,6 +60,8 @@ export declare class FixedClusterPool<Data = unknown, Response = unknown> extend
60
60
  /** @inheritDoc */
61
61
  get type(): PoolType;
62
62
  /** @inheritDoc */
63
+ get size(): number;
64
+ /** @inheritDoc */
63
65
  protected get full(): boolean;
64
66
  /** @inheritDoc */
65
67
  protected get busy(): boolean;
@@ -108,6 +108,10 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
108
108
  * If it is `'dynamic'`, it provides the `max` property.
109
109
  */
110
110
  readonly type: PoolType;
111
+ /**
112
+ * Pool maximum size.
113
+ */
114
+ readonly size: number;
111
115
  /**
112
116
  * Pool worker nodes.
113
117
  */
@@ -121,23 +125,14 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
121
125
  * - `'busy'`: Emitted when the pool is busy.
122
126
  */
123
127
  readonly emitter?: PoolEmitter;
124
- /**
125
- * Finds a free worker node key based on the number of tasks the worker has applied.
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
128
  /**
135
129
  * Executes the function specified in the worker constructor with the task data input parameter.
136
130
  *
137
131
  * @param data - The task input data for the specified worker function. This can only be serializable data.
132
+ * @param name - The name of the worker function to execute. If not specified, the default worker function will be executed.
138
133
  * @returns Promise that will be fulfilled when the task is completed.
139
134
  */
140
- execute: (data?: Data) => Promise<Response>;
135
+ execute: (data?: Data, name?: string) => Promise<Response>;
141
136
  /**
142
137
  * Shutdowns every current worker in this pool.
143
138
  */
@@ -1,4 +1,4 @@
1
- import { type IPool } from '../pool';
1
+ import type { IPool } from '../pool';
2
2
  import type { IWorker } from '../worker';
3
3
  import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
4
  /**
@@ -11,8 +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
- /** @inheritDoc */
15
- protected readonly isDynamicPool: boolean;
14
+ /**
15
+ * Toggles finding the last free worker node key.
16
+ */
17
+ private toggleFindLastFreeWorkerNodeKey;
16
18
  /** @inheritDoc */
17
19
  readonly requiredStatistics: RequiredStatistics;
18
20
  /**
@@ -26,9 +28,37 @@ export declare abstract class AbstractWorkerChoiceStrategy<Worker extends IWorke
26
28
  /** @inheritDoc */
27
29
  abstract reset(): boolean;
28
30
  /** @inheritDoc */
31
+ abstract update(workerNodeKey: number): boolean;
32
+ /** @inheritDoc */
29
33
  abstract choose(): number;
30
34
  /** @inheritDoc */
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
  }
@@ -14,21 +14,23 @@ export declare class FairShareWorkerChoiceStrategy<Worker extends IWorker, Data
14
14
  /** @inheritDoc */
15
15
  readonly requiredStatistics: RequiredStatistics;
16
16
  /**
17
- * Worker last virtual task execution timestamp.
17
+ * Workers' virtual task execution timestamp.
18
18
  */
19
- private readonly workerLastVirtualTaskTimestamp;
19
+ private workersVirtualTaskTimestamp;
20
20
  /** @inheritDoc */
21
21
  constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
22
22
  /** @inheritDoc */
23
23
  reset(): boolean;
24
24
  /** @inheritDoc */
25
+ update(workerNodeKey: number): boolean;
26
+ /** @inheritDoc */
25
27
  choose(): number;
26
28
  /** @inheritDoc */
27
29
  remove(workerNodeKey: number): boolean;
28
30
  /**
29
- * Computes worker last virtual task timestamp.
31
+ * Computes worker virtual task timestamp.
30
32
  *
31
33
  * @param workerNodeKey - The worker node key.
32
34
  */
33
- private computeWorkerLastVirtualTaskTimestamp;
35
+ private computeWorkerVirtualTaskTimestamp;
34
36
  }
@@ -17,7 +17,9 @@ export declare class LessBusyWorkerChoiceStrategy<Worker extends IWorker, Data =
17
17
  /** @inheritDoc */
18
18
  reset(): boolean;
19
19
  /** @inheritDoc */
20
+ update(): boolean;
21
+ /** @inheritDoc */
20
22
  choose(): number;
21
23
  /** @inheritDoc */
22
- remove(workerNodeKey: number): boolean;
24
+ remove(): boolean;
23
25
  }
@@ -15,7 +15,9 @@ export declare class LessUsedWorkerChoiceStrategy<Worker extends IWorker, Data =
15
15
  /** @inheritDoc */
16
16
  reset(): boolean;
17
17
  /** @inheritDoc */
18
+ update(): boolean;
19
+ /** @inheritDoc */
18
20
  choose(): number;
19
21
  /** @inheritDoc */
20
- remove(workerNodeKey: number): boolean;
22
+ remove(): boolean;
21
23
  }
@@ -19,6 +19,8 @@ export declare class RoundRobinWorkerChoiceStrategy<Worker extends IWorker, Data
19
19
  /** @inheritDoc */
20
20
  reset(): boolean;
21
21
  /** @inheritDoc */
22
+ update(): boolean;
23
+ /** @inheritDoc */
22
24
  choose(): number;
23
25
  /** @inheritDoc */
24
26
  remove(workerNodeKey: number): boolean;
@@ -37,6 +37,13 @@ export interface WorkerChoiceStrategyOptions {
37
37
  * @defaultValue false
38
38
  */
39
39
  medRunTime?: boolean;
40
+ /**
41
+ * Worker weights to use for weighted round robin worker selection strategy.
42
+ * Weight is the tasks maximum average or median runtime in milliseconds.
43
+ *
44
+ * @defaultValue Computed worker weights automatically given the CPU performance.
45
+ */
46
+ weights?: Record<number, number>;
40
47
  }
41
48
  /**
42
49
  * Pool worker tasks usage statistics requirements.
@@ -66,17 +73,28 @@ export interface IWorkerChoiceStrategy {
66
73
  */
67
74
  readonly requiredStatistics: RequiredStatistics;
68
75
  /**
69
- * Resets strategy internals (counters, statistics, etc.).
76
+ * Resets strategy internals.
77
+ *
78
+ * @returns `true` if the reset is successful, `false` otherwise.
70
79
  */
71
80
  reset: () => boolean;
81
+ /**
82
+ * Updates the worker node key strategy internals.
83
+ *
84
+ * @returns `true` if the update is successful, `false` otherwise.
85
+ */
86
+ update: (workerNodeKey: number) => boolean;
72
87
  /**
73
88
  * Chooses a worker node in the pool and returns its key.
89
+ *
90
+ * @returns The worker node key.
74
91
  */
75
92
  choose: () => number;
76
93
  /**
77
- * Removes a worker node key from strategy internals.
94
+ * Removes the worker node key from strategy internals.
78
95
  *
79
96
  * @param workerNodeKey - The worker node key.
97
+ * @returns `true` if the worker node key is removed, `false` otherwise.
80
98
  */
81
99
  remove: (workerNodeKey: number) => boolean;
82
100
  /**
@@ -1,7 +1,7 @@
1
1
  import type { IWorker } from '../worker';
2
+ import type { IPool } from '../pool';
2
3
  import { AbstractWorkerChoiceStrategy } from './abstract-worker-choice-strategy';
3
4
  import type { IWorkerChoiceStrategy, RequiredStatistics, WorkerChoiceStrategyOptions } from './selection-strategies-types';
4
- import type { IPool } from '../pool';
5
5
  /**
6
6
  * Selects the next worker with a weighted round robin scheduling algorithm.
7
7
  * Loosely modeled after the weighted round robin queueing algorithm: https://en.wikipedia.org/wiki/Weighted_round_robin.
@@ -22,20 +22,19 @@ export declare class WeightedRoundRobinWorkerChoiceStrategy<Worker extends IWork
22
22
  */
23
23
  private readonly defaultWorkerWeight;
24
24
  /**
25
- * Workers' virtual task runtime.
25
+ * Worker virtual task runtime.
26
26
  */
27
- private readonly workersTaskRunTime;
27
+ private workerVirtualTaskRunTime;
28
28
  /** @inheritDoc */
29
29
  constructor(pool: IPool<Worker, Data, Response>, opts?: WorkerChoiceStrategyOptions);
30
30
  /** @inheritDoc */
31
31
  reset(): boolean;
32
32
  /** @inheritDoc */
33
+ update(): boolean;
34
+ /** @inheritDoc */
33
35
  choose(): number;
34
36
  /** @inheritDoc */
35
37
  remove(workerNodeKey: number): boolean;
36
- private initWorkersTaskRunTime;
37
- private initWorkerTaskRunTime;
38
- private setWorkerTaskRunTime;
39
38
  private getWorkerVirtualTaskRunTime;
40
- private computeWorkerWeight;
39
+ private computeDefaultWorkerWeight;
41
40
  }
@@ -31,6 +31,12 @@ export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data =
31
31
  * @param workerChoiceStrategy - The worker choice strategy to set.
32
32
  */
33
33
  setWorkerChoiceStrategy(workerChoiceStrategy: WorkerChoiceStrategy): void;
34
+ /**
35
+ * Updates the worker node key in the worker choice strategy internals in the context.
36
+ *
37
+ * @returns `true` if the update is successful, `false` otherwise.
38
+ */
39
+ update(workerNodeKey: number): boolean;
34
40
  /**
35
41
  * Executes the worker choice strategy algorithm in the context.
36
42
  *
@@ -38,7 +44,7 @@ export declare class WorkerChoiceStrategyContext<Worker extends IWorker, Data =
38
44
  */
39
45
  execute(): number;
40
46
  /**
41
- * Removes a worker node key from the worker choice strategy in the context.
47
+ * Removes the worker node key from the worker choice strategy in the context.
42
48
  *
43
49
  * @param workerNodeKey - The key of the worker node.
44
50
  * @returns `true` if the removal is successful, `false` otherwise.
@@ -29,5 +29,7 @@ export declare class DynamicThreadPool<Data = unknown, Response = unknown> exten
29
29
  /** @inheritDoc */
30
30
  protected get full(): boolean;
31
31
  /** @inheritDoc */
32
+ get size(): number;
33
+ /** @inheritDoc */
32
34
  protected get busy(): boolean;
33
35
  }
@@ -44,6 +44,8 @@ export declare class FixedThreadPool<Data = unknown, Response = unknown> extends
44
44
  /** @inheritDoc */
45
45
  get type(): PoolType;
46
46
  /** @inheritDoc */
47
+ get size(): number;
48
+ /** @inheritDoc */
47
49
  protected get full(): boolean;
48
50
  /** @inheritDoc */
49
51
  protected get busy(): boolean;
@@ -1,4 +1,5 @@
1
1
  import type { CircularArray } from '../circular-array';
2
+ import type { Queue } from '../queue';
2
3
  /**
3
4
  * Callback invoked if the worker has received a message.
4
5
  */
@@ -22,12 +23,16 @@ export type ExitHandler<Worker extends IWorker> = (this: Worker, code: number) =
22
23
  * @internal
23
24
  */
24
25
  export interface Task<Data = unknown> {
26
+ /**
27
+ * Task name.
28
+ */
29
+ readonly name?: string;
25
30
  /**
26
31
  * Task input data that will be passed to the worker.
27
32
  */
28
33
  readonly data?: Data;
29
34
  /**
30
- * UUID of the message.
35
+ * Message UUID.
31
36
  */
32
37
  readonly id?: string;
33
38
  }
@@ -78,7 +83,7 @@ export interface IWorker {
78
83
  */
79
84
  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
85
  /**
81
- * Register a listener to the exit event that will only performed once.
86
+ * Register a listener to the exit event that will only be performed once.
82
87
  *
83
88
  * @param event - `'exit'`.
84
89
  * @param handler - The exit handler.
@@ -104,5 +109,5 @@ export interface WorkerNode<Worker extends IWorker, Data = unknown> {
104
109
  /**
105
110
  * Worker node tasks queue.
106
111
  */
107
- readonly tasksQueue: Array<Task<Data>>;
112
+ readonly tasksQueue: Queue<Task<Data>>;
108
113
  }
package/lib/queue.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Queue
3
+ *
4
+ * @typeParam T - Type of queue items.
5
+ */
6
+ export declare class Queue<T> {
7
+ private items;
8
+ private head;
9
+ private tail;
10
+ constructor();
11
+ /**
12
+ * Get the size of the queue.
13
+ *
14
+ * @returns The size of the queue.
15
+ * @readonly
16
+ */
17
+ get size(): number;
18
+ /**
19
+ * Enqueue an item.
20
+ *
21
+ * @param item - Item to enqueue.
22
+ * @returns The new size of the queue.
23
+ */
24
+ enqueue(item: T): number;
25
+ /**
26
+ * Dequeue an item.
27
+ *
28
+ * @returns The dequeued item or `undefined` if the queue is empty.
29
+ */
30
+ dequeue(): T | undefined;
31
+ }
@@ -19,7 +19,7 @@ export type Draft<T> = {
19
19
  * @typeParam MainWorker - Type of main worker.
20
20
  * @internal
21
21
  */
22
- export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker | MessagePort | unknown = unknown> extends Task<Data> {
22
+ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker | MessagePort = ClusterWorker | MessagePort> extends Task<Data> {
23
23
  /**
24
24
  * Kill code.
25
25
  */
@@ -60,6 +60,16 @@ export type WorkerAsyncFunction<Data = unknown, Response = unknown> = (data?: Da
60
60
  * @typeParam Response - Type of execution response. This can only be serializable data.
61
61
  */
62
62
  export type WorkerFunction<Data = unknown, Response = unknown> = WorkerSyncFunction<Data, Response> | WorkerAsyncFunction<Data, Response>;
63
+ /**
64
+ * Worker functions that can be executed.
65
+ * This object can contain synchronous or asynchronous functions.
66
+ * The key is the name of the function.
67
+ * The value is the function itself.
68
+ *
69
+ * @typeParam Data - Type of data sent to the worker. This can only be serializable data.
70
+ * @typeParam Response - Type of execution response. This can only be serializable data.
71
+ */
72
+ export type TaskFunctions<Data = unknown, Response = unknown> = Record<string, WorkerFunction<Data, Response>>;
63
73
  /**
64
74
  * An object holding the execution response promise resolve/reject callbacks.
65
75
  *
package/lib/utils.d.ts CHANGED
@@ -14,3 +14,4 @@ export declare const DEFAULT_WORKER_CHOICE_STRATEGY_OPTIONS: WorkerChoiceStrateg
14
14
  * @returns The median of the given data set.
15
15
  */
16
16
  export declare const median: (dataSet: number[]) => number;
17
+ export declare const isPlainObject: (obj: unknown) => boolean;
@@ -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, WorkerAsyncFunction, WorkerFunction, WorkerSyncFunction } from '../utility-types';
8
+ import type { MessageValue, TaskFunctions, 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.
@@ -18,6 +18,10 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
18
18
  protected readonly isMain: boolean;
19
19
  protected mainWorker: MainWorker | undefined | null;
20
20
  protected readonly opts: WorkerOptions;
21
+ /**
22
+ * Task function(s) processed by the worker when the pool's `execution` function is invoked.
23
+ */
24
+ protected taskFunctions: Map<string, WorkerFunction<Data, Response>>;
21
25
  /**
22
26
  * Timestamp of the last task processed by this worker.
23
27
  */
@@ -31,25 +35,24 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
31
35
  *
32
36
  * @param type - The type of async event.
33
37
  * @param isMain - Whether this is the main worker or not.
34
- * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
38
+ * @param taskFunctions - Task function(s) processed by the worker when the pool's `execution` function is invoked. The first function is the default function.
35
39
  * @param mainWorker - Reference to main worker.
36
40
  * @param opts - Options for the worker.
37
41
  */
38
- constructor(type: string, isMain: boolean, fn: WorkerFunction<Data, Response>, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
42
+ constructor(type: string, isMain: boolean, taskFunctions: WorkerFunction<Data, Response> | TaskFunctions<Data, Response>, mainWorker: MainWorker | undefined | null, opts?: WorkerOptions);
43
+ private checkWorkerOptions;
39
44
  /**
40
- * Worker message listener.
45
+ * Checks if the `taskFunctions` parameter is passed to the constructor.
41
46
  *
42
- * @param message - Message received.
43
- * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
47
+ * @param taskFunctions - The task function(s) parameter that should be checked.
44
48
  */
45
- protected messageListener(message: MessageValue<Data, MainWorker>, fn: WorkerFunction<Data, Response>): void;
46
- private checkWorkerOptions;
49
+ private checkTaskFunctions;
47
50
  /**
48
- * Checks if the `fn` parameter is passed to the constructor.
51
+ * Worker message listener.
49
52
  *
50
- * @param fn - The function that should be defined.
53
+ * @param message - Message received.
51
54
  */
52
- private checkFunctionInput;
55
+ protected messageListener(message: MessageValue<Data, MainWorker>): void;
53
56
  /**
54
57
  * Returns the main worker.
55
58
  *
@@ -79,7 +82,7 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
79
82
  * @param fn - Function that will be executed.
80
83
  * @param message - Input data for the given function.
81
84
  */
82
- protected run(fn: WorkerSyncFunction<Data, Response>, message: MessageValue<Data>): void;
85
+ protected runSync(fn: WorkerSyncFunction<Data, Response>, message: MessageValue<Data>): void;
83
86
  /**
84
87
  * Runs the given function asynchronously.
85
88
  *
@@ -87,4 +90,10 @@ export declare abstract class AbstractWorker<MainWorker extends Worker | Message
87
90
  * @param message - Input data for the given function.
88
91
  */
89
92
  protected runAsync(fn: WorkerAsyncFunction<Data, Response>, message: MessageValue<Data>): void;
93
+ /**
94
+ * Gets the task function in the given scope.
95
+ *
96
+ * @param name - Name of the function that will be returned.
97
+ */
98
+ private getTaskFunction;
90
99
  }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  import type { Worker } from 'node:cluster';
3
- import type { MessageValue, WorkerFunction } from '../utility-types';
3
+ import type { MessageValue, TaskFunctions, WorkerFunction } from '../utility-types';
4
4
  import { AbstractWorker } from './abstract-worker';
5
5
  import type { WorkerOptions } from './worker-options';
6
6
  /**
@@ -21,10 +21,10 @@ export declare class ClusterWorker<Data = unknown, Response = unknown> extends A
21
21
  /**
22
22
  * Constructs a new poolifier cluster worker.
23
23
  *
24
- * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
24
+ * @param taskFunctions - Task function(s) 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: WorkerFunction<Data, Response>, opts?: WorkerOptions);
27
+ constructor(taskFunctions: WorkerFunction<Data, Response> | TaskFunctions<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, WorkerFunction } from '../utility-types';
3
+ import type { MessageValue, TaskFunctions, WorkerFunction } from '../utility-types';
4
4
  import { AbstractWorker } from './abstract-worker';
5
5
  import type { WorkerOptions } from './worker-options';
6
6
  /**
@@ -21,10 +21,10 @@ export declare class ThreadWorker<Data = unknown, Response = unknown> extends Ab
21
21
  /**
22
22
  * Constructs a new poolifier thread worker.
23
23
  *
24
- * @param fn - Function processed by the worker when the pool's `execution` function is invoked.
24
+ * @param taskFunctions - Task function(s) 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: WorkerFunction<Data, Response>, opts?: WorkerOptions);
27
+ constructor(taskFunctions: WorkerFunction<Data, Response> | TaskFunctions<Data, Response>, opts?: WorkerOptions);
28
28
  /** @inheritDoc */
29
29
  protected sendToMainWorker(message: MessageValue<Response>): void;
30
30
  }
@@ -45,6 +45,7 @@ export interface WorkerOptions {
45
45
  * Whether your worker will perform asynchronous or not.
46
46
  *
47
47
  * @defaultValue false
48
+ * @deprecated This option will be removed in the next major version.
48
49
  */
49
50
  async?: boolean;
50
51
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.4.10",
3
+ "version": "2.4.12",
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,12 @@
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.1.0",
29
+ "pnpm": "8.4.0"
25
30
  },
26
31
  "repository": {
27
32
  "type": "git",
@@ -73,57 +78,56 @@
73
78
  "lib"
74
79
  ],
75
80
  "devDependencies": {
76
- "@commitlint/cli": "^17.6.1",
77
- "@commitlint/config-conventional": "^17.6.1",
81
+ "@commitlint/cli": "^17.6.3",
82
+ "@commitlint/config-conventional": "^17.6.3",
78
83
  "@release-it/bumper": "^4.0.2",
79
84
  "@release-it/keep-a-changelog": "^3.1.0",
80
85
  "@rollup/plugin-terser": "^0.4.1",
81
86
  "@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",
87
+ "@types/node": "^20.1.0",
88
+ "@typescript-eslint/eslint-plugin": "^5.59.2",
89
+ "@typescript-eslint/parser": "^5.59.2",
85
90
  "benny": "^3.7.1",
86
91
  "c8": "^7.13.0",
87
- "eslint": "^8.38.0",
92
+ "eslint": "^8.40.0",
88
93
  "eslint-config-standard": "^17.0.0",
89
94
  "eslint-config-standard-with-typescript": "^34.0.1",
90
- "eslint-define-config": "^1.18.0",
95
+ "eslint-define-config": "^1.20.0",
91
96
  "eslint-import-resolver-typescript": "^3.5.5",
92
97
  "eslint-plugin-import": "^2.27.5",
93
- "eslint-plugin-jsdoc": "^41.1.1",
98
+ "eslint-plugin-jsdoc": "^43.2.0",
94
99
  "eslint-plugin-n": "^15.7.0",
95
100
  "eslint-plugin-promise": "^6.1.1",
96
101
  "eslint-plugin-spellcheck": "^0.0.20",
97
102
  "eslint-plugin-tsdoc": "^0.2.17",
98
103
  "expect": "^29.5.0",
99
104
  "husky": "^8.0.3",
100
- "lint-staged": "^13.2.1",
105
+ "lint-staged": "^13.2.2",
101
106
  "microtime": "^3.1.1",
102
107
  "mocha": "^10.2.0",
103
108
  "mochawesome": "^7.1.3",
104
- "prettier": "^2.8.7",
105
- "prettier-plugin-organize-imports": "^3.2.2",
106
- "release-it": "^15.10.1",
107
- "rollup": "^3.20.2",
109
+ "prettier": "^2.8.8",
110
+ "release-it": "^15.10.3",
111
+ "rollup": "^3.21.5",
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.6",
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",