poolifier 2.5.1 → 2.5.2

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/lib/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { DynamicClusterPool } from './pools/cluster/dynamic';
2
2
  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
- export { PoolEvents } from './pools/pool';
6
- export type { IPool, PoolEmitter, PoolEvent, PoolOptions, PoolType, TasksQueueOptions } from './pools/pool';
5
+ export { PoolEvents, PoolTypes } from './pools/pool';
6
+ export type { IPool, PoolEmitter, PoolEvent, PoolInfo, 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';
package/lib/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=require("node:events"),r=require("node:cluster"),s=require("node:crypto"),i=require("node:os"),o=require("node:worker_threads"),n=require("node:async_hooks");!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(e||(e={}));class a extends t{}const h=Object.freeze({full:"full",busy:"busy",error:"error"}),u=Object.freeze((()=>{})),c={medRunTime:!1,medWaitTime:!1},k=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},d=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class m extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class p{items;head;tail;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}peek(){if(!(this.size<=0))return this.items[this.head]}}const g=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class T{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=c){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setRequiredStatistics(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!1,this.requiredStatistics.medWaitTime=e.medWaitTime),this.requiredStatistics.medWaitTime&&!1===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!0,this.requiredStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??c,this.setRequiredStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.requiredStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of i.cpus()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/i.cpus().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class w extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=c){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[r]&&this.computeWorkerVirtualTaskEndTimestamp(r);const s=this.workersVirtualTaskEndTimestamp[r];s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class W extends T{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=c){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let r=this.currentRoundId;r<this.roundWeights.length;r++)for(let s=this.currentWorkerNodeId;s<this.pool.workerNodes.length;s++){if((this.opts.weights?.[s]??this.defaultWorkerWeight)>=this.roundWeights[r]){e=r,t=s;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const r=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,r}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class f extends T{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=c){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class y extends T{constructor(e,t=c){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class N extends T{nextWorkerNodeId=0;constructor(e,t=c){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class S extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=c){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=g.ROUND_ROBIN,r=c){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new(N.bind(this))(e,r)],[g.LEAST_USED,new(y.bind(this))(e,r)],[g.LEAST_BUSY,new(f.bind(this))(e,r)],[g.FAIR_SHARE,new(w.bind(this))(e,r)],[g.WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,r)],[g.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(W.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class I{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new a),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(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(!d(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??g.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??c,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!d(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.size)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!d(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 m,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new m,avgWaitTime:0,medWaitTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const r=performance.now(),i=this.chooseWorkerNode(),o={name:t,data:e??{},submissionTimestamp:r,id:s.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:this.workerNodes[i].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[i].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(i,o):this.executeTask(i,o),this.workerChoiceStrategyContext.update(i),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--r.running,++r.run,null!=t.error&&++r.error,this.updateRunTimeTasksUsage(r,t),this.updateWaitTimeTasksUsage(r,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==e.run&&(e.avgRunTime=e.runTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=k(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgWaitTime&&0!==e.run&&(e.avgWaitTime=e.waitTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=k(e.waitTimeHistory)))}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}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(h.error,e)})),!0===this.opts.restartWorkerOnError&&e.on("error",(()=>{this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(h.busy),this.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 m,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new m,avgWaitTime:0,medWaitTime:0,error:0},tasksQueue:new p})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class E extends I{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 b extends I{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 v="default",x=6e4,O=l.SOFT;class C extends n.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:O,maxInactiveTime:x}){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??x)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??O,this.opts.maxInactiveTime=e.maxInactiveTime??x,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(v,e.bind(this));else{if(!d(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(v,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??x)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{const r=performance.now(),s=r-(t.submissionTimestamp??0),i=e(t.data),o=performance.now()-r;this.sendToMainWorker({data:i,id:t.id,runTime:o,waitTime:s})}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(),s=r-(t.submissionTimestamp??0);e(t.data).then((e=>{const i=performance.now()-r;return this.sendToMainWorker({data:e,id:t.id,runTime:i,waitTime: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??v;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 E{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 b{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=E,exports.FixedThreadPool=b,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=g;
1
+ "use strict";var e=require("node:events"),t=require("node:cluster"),r=require("node:crypto"),s=require("node:os"),i=require("node:worker_threads"),o=require("node:async_hooks");const n=Object.freeze({fixed:"fixed",dynamic:"dynamic"});class a extends e{}const h=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),u=Object.freeze((()=>{})),k={medRunTime:!1,medWaitTime:!1},d=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},c=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),l=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class m extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class p{items;head;tail;max;constructor(){this.items={},this.head=0,this.tail=0,this.max=0}get size(){return this.tail-this.head}get maxSize(){return this.max}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size>this.max&&(this.max=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}peek(){if(!(this.size<=0))return this.items[this.head]}}const g=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class T{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=k){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setRequiredStatistics(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!1,this.requiredStatistics.medWaitTime=e.medWaitTime),this.requiredStatistics.medWaitTime&&!1===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!0,this.requiredStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??k,this.setRequiredStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.requiredStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of s.cpus()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s.cpus().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class w extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[r]&&this.computeWorkerVirtualTaskEndTimestamp(r);const s=this.workersVirtualTaskEndTimestamp[r];s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class W extends T{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let r=this.currentRoundId;r<this.roundWeights.length;r++)for(let s=this.currentWorkerNodeId;s<this.pool.workerNodes.length;s++){if((this.opts.weights?.[s]??this.defaultWorkerWeight)>=this.roundWeights[r]){e=r,t=s;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const r=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,r}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class f extends T{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class y extends T{constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class N extends T{nextWorkerNodeId=0;constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class S extends T{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=k){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class R{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=g.ROUND_ROBIN,r=k){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[g.ROUND_ROBIN,new(N.bind(this))(e,r)],[g.LEAST_USED,new(y.bind(this))(e,r)],[g.LEAST_BUSY,new(f.bind(this))(e,r)],[g.FAIR_SHARE,new(w.bind(this))(e,r)],[g.WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,r)],[g.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(W.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class x{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new a),this.workerChoiceStrategyContext=new R(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===n.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!c(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??g.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??k,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(g).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!c(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!c(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.tasksUsage.running?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.tasksUsage.running>0?e+1:e),0),runningTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new m,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new m,avgWaitTime:0,medWaitTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const s=performance.now(),i=this.chooseWorkerNode(),o={name:t,data:e??{},submissionTimestamp:s,id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:this.workerNodes[i].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[i].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(i,o):this.executeTask(i,o),this.workerChoiceStrategyContext.update(i),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--r.running,++r.run,null!=t.error&&++r.error,this.updateRunTimeTasksUsage(r,t),this.updateWaitTimeTasksUsage(r,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==e.run&&(e.avgRunTime=e.runTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=d(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgWaitTime&&0!==e.run&&(e.avgWaitTime=e.waitTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=d(e.waitTimeHistory)))}chooseWorkerNode(){let e;if(this.type===n.dynamic&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const r=this.getWorkerNodeKey(t);var s;s=l.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].tasksUsage.running)&&(this.flushTasksQueue(r),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return e}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??u),e.on("error",this.opts.errorHandler??u),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(h.error,e)})),!0===this.opts.restartWorkerOnError&&e.on("error",(()=>{this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??u),e.on("exit",this.opts.exitHandler??u),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?(t.reject(e.error),null!=this.emitter&&this.emitter.emit(h.taskError,{error:e.error,errorData:e.errorData})):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(h.busy,this.info),this.type===n.dynamic&&this.full&&this.emitter?.emit(h.full,this.info))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new m,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new m,avgWaitTime:0,medWaitTime:0,error:0},tasksQueue:new p})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class E extends x{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return n.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class I extends x{constructor(e,t,r={}){super(e,t,r)}isMain(){return i.isMainThread}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new i.Worker(this.filePath,{env:i.SHARE_ENV})}afterWorkerSetup(e){const{port1:t,port2:r}=new i.MessageChannel;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return n.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}const b="default",v=6e4,O=l.SOFT;class C extends o.AsyncResource{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:O,maxInactiveTime:v}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??v)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??O,this.opts.maxInactiveTime=e.maxInactiveTime??v,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(b,e.bind(this));else{if(!c(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[r,s]of Object.entries(e)){if("function"!=typeof s)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(r,s.bind(this)),t&&(this.taskFunctions.set(b,s.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??v)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{const r=performance.now(),s=r-(t.submissionTimestamp??0),i=e(t.data),o=performance.now()-r;this.sendToMainWorker({data:i,runTime:o,waitTime:s,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now(),s=r-(t.submissionTimestamp??0);e(t.data).then((e=>{const i=performance.now()-r;return this.sendToMainWorker({data:e,runTime:i,waitTime:s,id:t.id}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(u)}getTaskFunction(e){e=e??b;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}}exports.ClusterWorker=class extends C{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}},exports.DynamicClusterPool=class extends E{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return n.dynamic}get maxSize(){return this.max}get full(){return this.workerNodes.length>=this.max}get busy(){return this.full&&this.internalBusy()}},exports.DynamicThreadPool=class extends I{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return n.dynamic}get full(){return this.workerNodes.length>=this.max}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}},exports.FixedClusterPool=E,exports.FixedThreadPool=I,exports.KillBehaviors=l,exports.PoolEvents=h,exports.PoolTypes=n,exports.ThreadWorker=class extends C{constructor(e,t={}){super("worker-thread-pool:poolifier",i.isMainThread,e,i.parentPort,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}},exports.WorkerChoiceStrategies=g;
package/lib/index.mjs CHANGED
@@ -1 +1 @@
1
- import e from"node:events";import t from"node:cluster";import r from"node:crypto";import{cpus as s}from"node:os";import{isMainThread as i,Worker as o,SHARE_ENV as n,MessageChannel as a,parentPort as h}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";var k;!function(e){e.FIXED="fixed",e.DYNAMIC="dynamic"}(k||(k={}));class c extends e{}const d=Object.freeze({full:"full",busy:"busy",error:"error"}),l=Object.freeze((()=>{})),m={medRunTime:!1,medWaitTime:!1},p=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),T=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class w extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class W{items;head;tail;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}peek(){if(!(this.size<=0))return this.items[this.head]}}const f=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class y{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=m){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setRequiredStatistics(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!1,this.requiredStatistics.medWaitTime=e.medWaitTime),this.requiredStatistics.medWaitTime&&!1===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!0,this.requiredStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??m,this.setRequiredStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.requiredStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of s()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class N extends y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[r]&&this.computeWorkerVirtualTaskEndTimestamp(r);const s=this.workersVirtualTaskEndTimestamp[r];s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends y{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let r=this.currentRoundId;r<this.roundWeights.length;r++)for(let s=this.currentWorkerNodeId;s<this.pool.workerNodes.length;s++){if((this.opts.weights?.[s]??this.defaultWorkerWeight)>=this.roundWeights[r]){e=r,t=s;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const r=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,r}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class R extends y{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class I extends y{constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class E extends y{nextWorkerNodeId=0;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class b extends y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class v{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=f.ROUND_ROBIN,r=m){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[f.ROUND_ROBIN,new(E.bind(this))(e,r)],[f.LEAST_USED,new(I.bind(this))(e,r)],[f.LEAST_BUSY,new(R.bind(this))(e,r)],[f.FAIR_SHARE,new(N.bind(this))(e,r)],[f.WEIGHTED_ROUND_ROBIN,new(b.bind(this))(e,r)],[f.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class O{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new c),this.workerChoiceStrategyContext=new v(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.FIXED&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??f.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??m,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(f).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.size)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get 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,waitTime:0,waitTimeHistory:new w,avgWaitTime:0,medWaitTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const s=performance.now(),i=this.chooseWorkerNode(),o={name:t,data:e??{},submissionTimestamp:s,id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:this.workerNodes[i].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[i].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(i,o):this.executeTask(i,o),this.workerChoiceStrategyContext.update(i),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--r.running,++r.run,null!=t.error&&++r.error,this.updateRunTimeTasksUsage(r,t),this.updateWaitTimeTasksUsage(r,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==e.run&&(e.avgRunTime=e.runTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=p(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgWaitTime&&0!==e.run&&(e.avgWaitTime=e.waitTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=p(e.waitTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.DYNAMIC&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const r=this.getWorkerNodeKey(t);var s;s=T.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].tasksUsage.running)&&(this.flushTasksQueue(r),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return e}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??l),e.on("error",this.opts.errorHandler??l),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(d.error,e)})),!0===this.opts.restartWorkerOnError&&e.on("error",(()=>{this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??l),e.on("exit",this.opts.exitHandler??l),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?t.reject(e.error):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(d.busy),this.type===k.DYNAMIC&&this.full&&this.emitter?.emit(d.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,waitTime:0,waitTimeHistory:new w,avgWaitTime:0,medWaitTime:0,error:0},tasksQueue:new W})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class x extends O{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get size(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class C extends x{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.DYNAMIC}get size(){return this.max}get full(){return this.workerNodes.length>=this.max}get busy(){return this.full&&this.internalBusy()}}class q extends O{constructor(e,t,r={}){super(e,t,r)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:r}=new a;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.FIXED}get size(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class A extends q{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.DYNAMIC}get full(){return this.workerNodes.length>=this.max}get size(){return this.max}get busy(){return this.full&&this.internalBusy()}}const F="default",M=6e4,U=T.SOFT;class Q extends u{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:U,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??U,this.opts.maxInactiveTime=e.maxInactiveTime??M,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(F,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[r,s]of Object.entries(e)){if("function"!=typeof s)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(r,s.bind(this)),t&&(this.taskFunctions.set(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=r-(t.submissionTimestamp??0),i=e(t.data),o=performance.now()-r;this.sendToMainWorker({data:i,id:t.id,runTime:o,waitTime:s})}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(),s=r-(t.submissionTimestamp??0);e(t.data).then((e=>{const i=performance.now()-r;return this.sendToMainWorker({data:e,id:t.id,runTime:i,waitTime:s}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(l)}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 D extends Q{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 z extends Q{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,h,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{D as ClusterWorker,C as DynamicClusterPool,A as DynamicThreadPool,x as FixedClusterPool,q as FixedThreadPool,T as KillBehaviors,d as PoolEvents,z as ThreadWorker,f 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 a,parentPort as h}from"node:worker_threads";import{AsyncResource as u}from"node:async_hooks";const k=Object.freeze({fixed:"fixed",dynamic:"dynamic"});class d extends e{}const c=Object.freeze({full:"full",busy:"busy",error:"error",taskError:"taskError"}),l=Object.freeze((()=>{})),m={medRunTime:!1,medWaitTime:!1},p=e=>{if(Array.isArray(e)&&0===e.length)return 0;if(Array.isArray(e)&&1===e.length)return e[0];const t=e.slice().sort(((e,t)=>e-t));return(t[t.length-1>>1]+t[t.length>>1])/2},g=e=>"object"==typeof e&&null!==e&&e?.constructor===Object&&"[object Object]"===Object.prototype.toString.call(e),T=Object.freeze({SOFT:"SOFT",HARD:"HARD"});class w extends Array{size;constructor(e=1024,...t){super(),this.checkSize(e),this.size=e,arguments.length>1&&this.push(...t)}push(...e){const t=super.push(...e);return t>this.size&&super.splice(0,t-this.size),this.length}unshift(...e){return super.unshift(...e)>this.size&&super.splice(this.size,e.length),this.length}concat(...e){const t=super.concat(e);return t.size=this.size,t.length>t.size&&t.splice(0,t.length-t.size),t}splice(e,t,...r){let s;return arguments.length>=3&&void 0!==t?(s=super.splice(e,t),this.push(...r)):s=2===arguments.length?super.splice(e,t):super.splice(e),s}resize(e){if(this.checkSize(e),0===e)this.length=0;else if(e<this.size)for(let t=e;t<this.size;t++)super.pop();this.size=e}empty(){return 0===this.length}full(){return this.length===this.size}checkSize(e){if(!Number.isSafeInteger(e))throw new TypeError(`Invalid circular array size: ${e} is not a safe integer`);if(e<0)throw new RangeError(`Invalid circular array size: ${e} < 0`)}}class W{items;head;tail;max;constructor(){this.items={},this.head=0,this.tail=0,this.max=0}get size(){return this.tail-this.head}get maxSize(){return this.max}enqueue(e){return this.items[this.tail]=e,this.tail++,this.size>this.max&&(this.max=this.size),this.size}dequeue(){if(this.size<=0)return;const e=this.items[this.head];return delete this.items[this.head],this.head++,this.head===this.tail&&(this.head=0,this.tail=0),e}peek(){if(!(this.size<=0))return this.items[this.head]}}const f=Object.freeze({ROUND_ROBIN:"ROUND_ROBIN",LEAST_USED:"LEAST_USED",LEAST_BUSY:"LEAST_BUSY",FAIR_SHARE:"FAIR_SHARE",WEIGHTED_ROUND_ROBIN:"WEIGHTED_ROUND_ROBIN",INTERLEAVED_WEIGHTED_ROUND_ROBIN:"INTERLEAVED_WEIGHTED_ROUND_ROBIN"});class y{pool;opts;toggleFindLastFreeWorkerNodeKey=!1;requiredStatistics={runTime:!1,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=m){this.pool=e,this.opts=t,this.choose=this.choose.bind(this)}setRequiredStatistics(e){this.requiredStatistics.avgRunTime&&!0===e.medRunTime&&(this.requiredStatistics.avgRunTime=!1,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.medRunTime&&!1===e.medRunTime&&(this.requiredStatistics.avgRunTime=!0,this.requiredStatistics.medRunTime=e.medRunTime),this.requiredStatistics.avgWaitTime&&!0===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!1,this.requiredStatistics.medWaitTime=e.medWaitTime),this.requiredStatistics.medWaitTime&&!1===e.medWaitTime&&(this.requiredStatistics.avgWaitTime=!0,this.requiredStatistics.medWaitTime=e.medWaitTime)}setOptions(e){e=e??m,this.setRequiredStatistics(e),this.opts=e}findFreeWorkerNodeKey(){return this.toggleFindLastFreeWorkerNodeKey?(this.toggleFindLastFreeWorkerNodeKey=!1,this.findLastFreeWorkerNodeKey()):(this.toggleFindLastFreeWorkerNodeKey=!0,this.findFirstFreeWorkerNodeKey())}getWorkerTaskRunTime(e){return this.requiredStatistics.medRunTime?this.pool.workerNodes[e].tasksUsage.medRunTime:this.pool.workerNodes[e].tasksUsage.avgRunTime}getWorkerWaitTime(e){return this.requiredStatistics.medWaitTime?this.pool.workerNodes[e].tasksUsage.medWaitTime:this.pool.workerNodes[e].tasksUsage.avgWaitTime}computeDefaultWorkerWeight(){let e=0;for(const t of s()){const r=t.speed.toString().length-1;e+=1/(t.speed/Math.pow(10,r))*Math.pow(10,r)}return Math.round(e/s().length)}findFirstFreeWorkerNodeKey(){return this.pool.workerNodes.findIndex((e=>0===e.tasksUsage.running))}findLastFreeWorkerNodeKey(){for(let e=this.pool.workerNodes.length-1;e>=0;e--)if(0===this.pool.workerNodes[e].tasksUsage.running)return e;return-1}}class N extends y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};workersVirtualTaskEndTimestamp=[];constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.workersVirtualTaskEndTimestamp=[],!0}update(e){return this.computeWorkerVirtualTaskEndTimestamp(e),!0}choose(){let e,t=1/0;for(const[r]of this.pool.workerNodes.entries()){null==this.workersVirtualTaskEndTimestamp[r]&&this.computeWorkerVirtualTaskEndTimestamp(r);const s=this.workersVirtualTaskEndTimestamp[r];s<t&&(t=s,e=r)}return e}remove(e){return this.workersVirtualTaskEndTimestamp.splice(e,1),!0}computeWorkerVirtualTaskEndTimestamp(e){this.workersVirtualTaskEndTimestamp[e]=this.getWorkerVirtualTaskEndTimestamp(e,this.getWorkerVirtualTaskStartTimestamp(e))}getWorkerVirtualTaskEndTimestamp(e,t){return t+this.getWorkerTaskRunTime(e)}getWorkerVirtualTaskStartTimestamp(e){return Math.max(performance.now(),this.workersVirtualTaskEndTimestamp[e]??-1/0)}}class S extends y{currentWorkerNodeId=0;currentRoundId=0;roundWeights;defaultWorkerWeight;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight(),this.roundWeights=this.getRoundWeights()}reset(){return this.currentWorkerNodeId=0,this.currentRoundId=0,!0}update(){return!0}choose(){let e,t;for(let r=this.currentRoundId;r<this.roundWeights.length;r++)for(let s=this.currentWorkerNodeId;s<this.pool.workerNodes.length;s++){if((this.opts.weights?.[s]??this.defaultWorkerWeight)>=this.roundWeights[r]){e=r,t=s;break}}this.currentRoundId=e??0,this.currentWorkerNodeId=t??0;const r=this.currentWorkerNodeId;return this.currentWorkerNodeId===this.pool.workerNodes.length-1?(this.currentWorkerNodeId=0,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1):this.currentWorkerNodeId=this.currentWorkerNodeId+1,r}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1,this.currentRoundId=this.currentRoundId===this.roundWeights.length-1?0:this.currentRoundId+1)),!0}setOptions(e){super.setOptions(e),this.roundWeights=this.getRoundWeights()}getRoundWeights(){return null==this.opts.weights?[this.defaultWorkerWeight]:[...new Set(Object.values(this.opts.weights).slice().sort(((e,t)=>e-t)))]}}class R extends y{requiredStatistics={runTime:!0,avgRunTime:!1,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage.runTime;if(0===i)return e;i<r&&(r=i,t=e)}return t}remove(){return!0}}class x extends y{constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return!0}update(){return!0}choose(){const e=this.findFreeWorkerNodeKey();if(-1!==e)return e;let t,r=1/0;for(const[e,s]of this.pool.workerNodes.entries()){const i=s.tasksUsage,o=i.run+i.running;if(0===o)return e;o<r&&(r=o,t=e)}return t}remove(){return!0}}class I extends y{nextWorkerNodeId=0;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts)}reset(){return this.nextWorkerNodeId=0,!0}update(){return!0}choose(){const e=this.nextWorkerNodeId;return this.nextWorkerNodeId=this.nextWorkerNodeId===this.pool.workerNodes.length-1?0:this.nextWorkerNodeId+1,e}remove(e){return this.nextWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.nextWorkerNodeId=0:this.nextWorkerNodeId>this.pool.workerNodes.length-1&&(this.nextWorkerNodeId=this.pool.workerNodes.length-1)),!0}}class E extends y{requiredStatistics={runTime:!0,avgRunTime:!0,medRunTime:!1,waitTime:!1,avgWaitTime:!1,medWaitTime:!1};currentWorkerNodeId=0;defaultWorkerWeight;workerVirtualTaskRunTime=0;constructor(e,t=m){super(e,t),this.setRequiredStatistics(this.opts),this.defaultWorkerWeight=this.computeDefaultWorkerWeight()}reset(){return this.currentWorkerNodeId=0,this.workerVirtualTaskRunTime=0,!0}update(){return!0}choose(){const e=this.currentWorkerNodeId,t=this.workerVirtualTaskRunTime;return t<(this.opts.weights?.[e]??this.defaultWorkerWeight)?this.workerVirtualTaskRunTime=t+this.getWorkerTaskRunTime(e):(this.currentWorkerNodeId=this.currentWorkerNodeId===this.pool.workerNodes.length-1?0:this.currentWorkerNodeId+1,this.workerVirtualTaskRunTime=0),e}remove(e){return this.currentWorkerNodeId===e&&(0===this.pool.workerNodes.length?this.currentWorkerNodeId=0:this.currentWorkerNodeId>this.pool.workerNodes.length-1&&(this.currentWorkerNodeId=this.pool.workerNodes.length-1),this.workerVirtualTaskRunTime=0),!0}}class b{workerChoiceStrategy;workerChoiceStrategies;constructor(e,t=f.ROUND_ROBIN,r=m){this.workerChoiceStrategy=t,this.execute=this.execute.bind(this),this.workerChoiceStrategies=new Map([[f.ROUND_ROBIN,new(I.bind(this))(e,r)],[f.LEAST_USED,new(x.bind(this))(e,r)],[f.LEAST_BUSY,new(R.bind(this))(e,r)],[f.FAIR_SHARE,new(N.bind(this))(e,r)],[f.WEIGHTED_ROUND_ROBIN,new(E.bind(this))(e,r)],[f.INTERLEAVED_WEIGHTED_ROUND_ROBIN,new(S.bind(this))(e,r)]])}getRequiredStatistics(){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).requiredStatistics}setWorkerChoiceStrategy(e){this.workerChoiceStrategy!==e&&(this.workerChoiceStrategy=e),this.workerChoiceStrategies.get(this.workerChoiceStrategy)?.reset()}update(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).update(e)}execute(){const e=this.workerChoiceStrategies.get(this.workerChoiceStrategy).choose();if(null==e)throw new Error("Worker node key chosen is null or undefined");return e}remove(e){return this.workerChoiceStrategies.get(this.workerChoiceStrategy).remove(e)}setOptions(e){for(const t of this.workerChoiceStrategies.values())t.setOptions(e)}}class O{numberOfWorkers;filePath;opts;workerNodes=[];emitter;promiseResponseMap=new Map;workerChoiceStrategyContext;constructor(e,t,r){if(this.numberOfWorkers=e,this.filePath=t,this.opts=r,!this.isMain())throw new Error("Cannot start a pool from a worker!");this.checkNumberOfWorkers(this.numberOfWorkers),this.checkFilePath(this.filePath),this.checkPoolOptions(this.opts),this.chooseWorkerNode=this.chooseWorkerNode.bind(this),this.executeTask=this.executeTask.bind(this),this.enqueueTask=this.enqueueTask.bind(this),this.checkAndEmitEvents=this.checkAndEmitEvents.bind(this),this.setupHook();for(let e=1;e<=this.numberOfWorkers;e++)this.createAndSetupWorker();!0===this.opts.enableEvents&&(this.emitter=new d),this.workerChoiceStrategyContext=new b(this,this.opts.workerChoiceStrategy,this.opts.workerChoiceStrategyOptions)}checkFilePath(e){if(null==e||"string"==typeof e&&0===e.trim().length)throw new Error("Please specify a file with a worker implementation")}checkNumberOfWorkers(e){if(null==e)throw new Error("Cannot instantiate a pool without specifying the number of workers");if(!Number.isSafeInteger(e))throw new TypeError("Cannot instantiate a pool with a non safe integer number of workers");if(e<0)throw new RangeError("Cannot instantiate a pool with a negative number of workers");if(this.type===k.fixed&&0===e)throw new Error("Cannot instantiate a fixed pool with no worker")}checkPoolOptions(e){if(!g(e))throw new TypeError("Invalid pool options: must be a plain object");this.opts.workerChoiceStrategy=e.workerChoiceStrategy??f.ROUND_ROBIN,this.checkValidWorkerChoiceStrategy(this.opts.workerChoiceStrategy),this.opts.workerChoiceStrategyOptions=e.workerChoiceStrategyOptions??m,this.checkValidWorkerChoiceStrategyOptions(this.opts.workerChoiceStrategyOptions),this.opts.restartWorkerOnError=e.restartWorkerOnError??!0,this.opts.enableEvents=e.enableEvents??!0,this.opts.enableTasksQueue=e.enableTasksQueue??!1,this.opts.enableTasksQueue&&(this.checkValidTasksQueueOptions(e.tasksQueueOptions),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e.tasksQueueOptions))}checkValidWorkerChoiceStrategy(e){if(!Object.values(f).includes(e))throw new Error(`Invalid worker choice strategy '${e}'`)}checkValidWorkerChoiceStrategyOptions(e){if(!g(e))throw new TypeError("Invalid worker choice strategy options: must be a plain object");if(null!=e.weights&&Object.keys(e.weights).length!==this.maxSize)throw new Error("Invalid worker choice strategy options: must have a weight for each worker node")}checkValidTasksQueueOptions(e){if(null!=e&&!g(e))throw new TypeError("Invalid tasks queue options: must be a plain object");if(e?.concurrency<=0)throw new Error(`Invalid worker tasks concurrency '${e.concurrency}'`)}get info(){return{type:this.type,minSize:this.minSize,maxSize:this.maxSize,workerNodes:this.workerNodes.length,idleWorkerNodes:this.workerNodes.reduce(((e,t)=>0===t.tasksUsage.running?e+1:e),0),busyWorkerNodes:this.workerNodes.reduce(((e,t)=>t.tasksUsage.running>0?e+1:e),0),runningTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksUsage.running),0),queuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.size),0),maxQueuedTasks:this.workerNodes.reduce(((e,t)=>e+t.tasksQueue.maxSize),0)}}getWorkerNodeKey(e){return this.workerNodes.findIndex((t=>t.worker===e))}setWorkerChoiceStrategy(e,t){this.checkValidWorkerChoiceStrategy(e),this.opts.workerChoiceStrategy=e;for(const e of this.workerNodes)this.setWorkerNodeTasksUsage(e,{run:0,running:0,runTime:0,runTimeHistory:new w,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new w,avgWaitTime:0,medWaitTime:0,error:0});this.workerChoiceStrategyContext.setWorkerChoiceStrategy(this.opts.workerChoiceStrategy),null!=t&&this.setWorkerChoiceStrategyOptions(t)}setWorkerChoiceStrategyOptions(e){this.checkValidWorkerChoiceStrategyOptions(e),this.opts.workerChoiceStrategyOptions=e,this.workerChoiceStrategyContext.setOptions(this.opts.workerChoiceStrategyOptions)}enableTasksQueue(e,t){!0!==this.opts.enableTasksQueue||e||this.flushTasksQueues(),this.opts.enableTasksQueue=e,this.setTasksQueueOptions(t)}setTasksQueueOptions(e){!0===this.opts.enableTasksQueue?(this.checkValidTasksQueueOptions(e),this.opts.tasksQueueOptions=this.buildTasksQueueOptions(e)):delete this.opts.tasksQueueOptions}buildTasksQueueOptions(e){return{concurrency:e?.concurrency??1}}internalBusy(){return-1===this.workerNodes.findIndex((e=>0===e.tasksUsage.running))}async execute(e,t){const s=performance.now(),i=this.chooseWorkerNode(),o={name:t,data:e??{},submissionTimestamp:s,id:r.randomUUID()},n=new Promise(((e,t)=>{this.promiseResponseMap.set(o.id,{resolve:e,reject:t,worker:this.workerNodes[i].worker})}));return!0===this.opts.enableTasksQueue&&(this.busy||this.workerNodes[i].tasksUsage.running>=this.opts.tasksQueueOptions.concurrency)?this.enqueueTask(i,o):this.executeTask(i,o),this.workerChoiceStrategyContext.update(i),this.checkAndEmitEvents(),n}async destroy(){await Promise.all(this.workerNodes.map((async(e,t)=>{this.flushTasksQueue(t),await this.destroyWorker(e.worker)})))}setupHook(){}beforeTaskExecutionHook(e){++this.workerNodes[e].tasksUsage.running}afterTaskExecutionHook(e,t){const r=this.workerNodes[this.getWorkerNodeKey(e)].tasksUsage;--r.running,++r.run,null!=t.error&&++r.error,this.updateRunTimeTasksUsage(r,t),this.updateWaitTimeTasksUsage(r,t)}updateRunTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().runTime&&(e.runTime+=t.runTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgRunTime&&0!==e.run&&(e.avgRunTime=e.runTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medRunTime&&null!=t.runTime&&(e.runTimeHistory.push(t.runTime),e.medRunTime=p(e.runTimeHistory)))}updateWaitTimeTasksUsage(e,t){this.workerChoiceStrategyContext.getRequiredStatistics().waitTime&&(e.waitTime+=t.waitTime??0,this.workerChoiceStrategyContext.getRequiredStatistics().avgWaitTime&&0!==e.run&&(e.avgWaitTime=e.waitTime/e.run),this.workerChoiceStrategyContext.getRequiredStatistics().medWaitTime&&null!=t.waitTime&&(e.waitTimeHistory.push(t.waitTime),e.medWaitTime=p(e.waitTimeHistory)))}chooseWorkerNode(){let e;if(this.type===k.dynamic&&!this.full&&this.internalBusy()){const t=this.createAndSetupWorker();this.registerWorkerMessageListener(t,(e=>{const r=this.getWorkerNodeKey(t);var s;s=T.HARD,(e.kill===s||null!=e.kill&&0===this.workerNodes[r].tasksUsage.running)&&(this.flushTasksQueue(r),this.destroyWorker(t))})),e=this.getWorkerNodeKey(t)}else e=this.workerChoiceStrategyContext.execute();return e}createAndSetupWorker(){const e=this.createWorker();return e.on("message",this.opts.messageHandler??l),e.on("error",this.opts.errorHandler??l),e.on("error",(e=>{null!=this.emitter&&this.emitter.emit(c.error,e)})),!0===this.opts.restartWorkerOnError&&e.on("error",(()=>{this.createAndSetupWorker()})),e.on("online",this.opts.onlineHandler??l),e.on("exit",this.opts.exitHandler??l),e.once("exit",(()=>{this.removeWorkerNode(e)})),this.pushWorkerNode(e),this.afterWorkerSetup(e),e}workerListener(){return e=>{if(null!=e.id){const t=this.promiseResponseMap.get(e.id);if(null!=t){null!=e.error?(t.reject(e.error),null!=this.emitter&&this.emitter.emit(c.taskError,{error:e.error,errorData:e.errorData})):t.resolve(e.data),this.afterTaskExecutionHook(t.worker,e),this.promiseResponseMap.delete(e.id);const r=this.getWorkerNodeKey(t.worker);!0===this.opts.enableTasksQueue&&this.tasksQueueSize(r)>0&&this.executeTask(r,this.dequeueTask(r))}}}}checkAndEmitEvents(){null!=this.emitter&&(this.busy&&this.emitter?.emit(c.busy,this.info),this.type===k.dynamic&&this.full&&this.emitter?.emit(c.full,this.info))}setWorkerNodeTasksUsage(e,t){e.tasksUsage=t}pushWorkerNode(e){return this.workerNodes.push({worker:e,tasksUsage:{run:0,running:0,runTime:0,runTimeHistory:new w,avgRunTime:0,medRunTime:0,waitTime:0,waitTimeHistory:new w,avgWaitTime:0,medWaitTime:0,error:0},tasksQueue:new W})}setWorkerNode(e,t,r,s){this.workerNodes[e]={worker:t,tasksUsage:r,tasksQueue:s}}removeWorkerNode(e){const t=this.getWorkerNodeKey(e);-1!==t&&(this.workerNodes.splice(t,1),this.workerChoiceStrategyContext.remove(t))}executeTask(e,t){this.beforeTaskExecutionHook(e),this.sendToWorker(this.workerNodes[e].worker,t)}enqueueTask(e,t){return this.workerNodes[e].tasksQueue.enqueue(t)}dequeueTask(e){return this.workerNodes[e].tasksQueue.dequeue()}tasksQueueSize(e){return this.workerNodes[e].tasksQueue.size}flushTasksQueue(e){if(this.tasksQueueSize(e)>0)for(let t=0;t<this.tasksQueueSize(e);t++)this.executeTask(e,this.dequeueTask(e))}flushTasksQueues(){for(const[e]of this.workerNodes.entries())this.flushTasksQueue(e)}}class v extends O{opts;constructor(e,t,r={}){super(e,t,r),this.opts=r}setupHook(){t.setupPrimary({...this.opts.settings,exec:this.filePath})}isMain(){return t.isPrimary}destroyWorker(e){this.sendToWorker(e,{kill:1}),e.kill()}sendToWorker(e,t){e.send(t)}registerWorkerMessageListener(e,t){e.on("message",t)}createWorker(){return t.fork(this.opts.env)}afterWorkerSetup(e){this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class C extends v{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.dynamic}get maxSize(){return this.max}get full(){return this.workerNodes.length>=this.max}get busy(){return this.full&&this.internalBusy()}}class z extends O{constructor(e,t,r={}){super(e,t,r)}isMain(){return i}async destroyWorker(e){this.sendToWorker(e,{kill:1}),await e.terminate()}sendToWorker(e,t){e.postMessage(t)}registerWorkerMessageListener(e,t){e.port2?.on("message",t)}createWorker(){return new o(this.filePath,{env:n})}afterWorkerSetup(e){const{port1:t,port2:r}=new a;e.postMessage({parent:t},[t]),e.port1=t,e.port2=r,this.registerWorkerMessageListener(e,super.workerListener())}get type(){return k.fixed}get minSize(){return this.numberOfWorkers}get maxSize(){return this.numberOfWorkers}get full(){return this.workerNodes.length>=this.numberOfWorkers}get busy(){return this.internalBusy()}}class q extends z{max;constructor(e,t,r,s={}){super(e,r,s),this.max=t}get type(){return k.dynamic}get full(){return this.workerNodes.length>=this.max}get maxSize(){return this.max}get busy(){return this.full&&this.internalBusy()}}const U="default",Q=6e4,A=T.SOFT;class F extends u{isMain;mainWorker;opts;taskFunctions;lastTaskTimestamp;aliveInterval;constructor(e,t,r,s,i={killBehavior:A,maxInactiveTime:Q}){super(e),this.isMain=t,this.mainWorker=s,this.opts=i,this.checkWorkerOptions(this.opts),this.checkTaskFunctions(r),this.isMain||(this.lastTaskTimestamp=performance.now(),this.aliveInterval=setInterval(this.checkAlive.bind(this),(this.opts.maxInactiveTime??Q)/2),this.checkAlive.bind(this)()),this.mainWorker?.on("message",this.messageListener.bind(this))}checkWorkerOptions(e){this.opts.killBehavior=e.killBehavior??A,this.opts.maxInactiveTime=e.maxInactiveTime??Q,delete this.opts.async}checkTaskFunctions(e){if(null==e)throw new Error("taskFunctions parameter is mandatory");if(this.taskFunctions=new Map,"function"==typeof e)this.taskFunctions.set(U,e.bind(this));else{if(!g(e))throw new TypeError("taskFunctions parameter is not a function or a plain object");{let t=!0;for(const[r,s]of Object.entries(e)){if("function"!=typeof s)throw new TypeError("A taskFunctions parameter object value is not a function");this.taskFunctions.set(r,s.bind(this)),t&&(this.taskFunctions.set(U,s.bind(this)),t=!1)}if(t)throw new Error("taskFunctions parameter object is empty")}}}messageListener(e){if(null!=e.id&&null!=e.data){const t=this.getTaskFunction(e.name);"AsyncFunction"===t?.constructor.name?this.runInAsyncScope(this.runAsync.bind(this),this,t,e):this.runInAsyncScope(this.runSync.bind(this),this,t,e)}else null!=e.parent?this.mainWorker=e.parent:null!=e.kill&&(null!=this.aliveInterval&&clearInterval(this.aliveInterval),this.emitDestroy())}getMainWorker(){if(null==this.mainWorker)throw new Error("Main worker was not set");return this.mainWorker}checkAlive(){performance.now()-this.lastTaskTimestamp>(this.opts.maxInactiveTime??Q)&&this.sendToMainWorker({kill:this.opts.killBehavior})}handleError(e){return e}runSync(e,t){try{const r=performance.now(),s=r-(t.submissionTimestamp??0),i=e(t.data),o=performance.now()-r;this.sendToMainWorker({data:i,runTime:o,waitTime:s,id:t.id})}catch(e){const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})}finally{!this.isMain&&(this.lastTaskTimestamp=performance.now())}}runAsync(e,t){const r=performance.now(),s=r-(t.submissionTimestamp??0);e(t.data).then((e=>{const i=performance.now()-r;return this.sendToMainWorker({data:e,runTime:i,waitTime:s,id:t.id}),null})).catch((e=>{const r=this.handleError(e);this.sendToMainWorker({error:r,errorData:t.data,id:t.id})})).finally((()=>{!this.isMain&&(this.lastTaskTimestamp=performance.now())})).catch(l)}getTaskFunction(e){e=e??U;const t=this.taskFunctions.get(e);if(null==t)throw new Error(`Task function '${e}' not found`);return t}}class M extends F{constructor(e,r={}){super("worker-cluster-pool:poolifier",t.isPrimary,e,t.worker,r)}sendToMainWorker(e){this.getMainWorker().send(e)}handleError(e){return e instanceof Error?e.message:e}}class D extends F{constructor(e,t={}){super("worker-thread-pool:poolifier",i,e,h,t)}sendToMainWorker(e){this.getMainWorker().postMessage(e)}}export{M as ClusterWorker,C as DynamicClusterPool,q as DynamicThreadPool,v as FixedClusterPool,z as FixedThreadPool,T as KillBehaviors,c as PoolEvents,k as PoolTypes,D as ThreadWorker,f as WorkerChoiceStrategies};
@@ -1,5 +1,5 @@
1
1
  import type { MessageValue, PromiseResponseWrapper } from '../utility-types';
2
- import { type IPool, PoolEmitter, type PoolOptions, PoolType, type TasksQueueOptions } from './pool';
2
+ import { type IPool, PoolEmitter, type PoolInfo, type PoolOptions, type PoolType, type TasksQueueOptions } from './pool';
3
3
  import type { IWorker, WorkerNode } from './worker';
4
4
  import { type WorkerChoiceStrategy, type WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types';
5
5
  import { WorkerChoiceStrategyContext } from './selection-strategies/worker-choice-strategy-context';
@@ -50,15 +50,15 @@ export declare abstract class AbstractPool<Worker extends IWorker, Data = unknow
50
50
  /** @inheritDoc */
51
51
  abstract get type(): PoolType;
52
52
  /** @inheritDoc */
53
- abstract get size(): number;
53
+ get info(): PoolInfo;
54
54
  /**
55
- * Number of tasks running in the pool.
55
+ * Pool minimum size.
56
56
  */
57
- private get numberOfRunningTasks();
57
+ protected abstract get minSize(): number;
58
58
  /**
59
- * Number of tasks queued in the pool.
59
+ * Pool maximum size.
60
60
  */
61
- private get numberOfQueuedTasks();
61
+ protected abstract get maxSize(): number;
62
62
  /**
63
63
  * Gets the given worker its worker node key.
64
64
  *
@@ -1,6 +1,5 @@
1
- import { PoolType } from '../pool';
2
- import type { ClusterPoolOptions } from './fixed';
3
- import { FixedClusterPool } from './fixed';
1
+ import { type PoolType } from '../pool';
2
+ import { type ClusterPoolOptions, FixedClusterPool } from './fixed';
4
3
  /**
5
4
  * A cluster pool with a dynamic number of workers, but a guaranteed minimum number of workers.
6
5
  *
@@ -26,7 +25,7 @@ export declare class DynamicClusterPool<Data = unknown, Response = unknown> exte
26
25
  /** @inheritDoc */
27
26
  get type(): PoolType;
28
27
  /** @inheritDoc */
29
- get size(): number;
28
+ protected get maxSize(): number;
30
29
  /** @inheritDoc */
31
30
  protected get full(): boolean;
32
31
  /** @inheritDoc */
@@ -2,8 +2,7 @@
2
2
  import type { ClusterSettings, Worker } from 'node:cluster';
3
3
  import type { MessageValue } from '../../utility-types';
4
4
  import { AbstractPool } from '../abstract-pool';
5
- import type { PoolOptions } from '../pool';
6
- import { PoolType } from '../pool';
5
+ import { type PoolOptions, type PoolType } from '../pool';
7
6
  /**
8
7
  * Options for a poolifier cluster pool.
9
8
  */
@@ -60,7 +59,9 @@ export declare class FixedClusterPool<Data = unknown, Response = unknown> extend
60
59
  /** @inheritDoc */
61
60
  get type(): PoolType;
62
61
  /** @inheritDoc */
63
- get size(): number;
62
+ protected get minSize(): number;
63
+ /** @inheritDoc */
64
+ protected get maxSize(): number;
64
65
  /** @inheritDoc */
65
66
  protected get full(): boolean;
66
67
  /** @inheritDoc */
@@ -3,21 +3,22 @@ import EventEmitterAsyncResource from 'node:events';
3
3
  import type { ErrorHandler, ExitHandler, IWorker, MessageHandler, OnlineHandler, WorkerNode } from './worker';
4
4
  import type { WorkerChoiceStrategy, WorkerChoiceStrategyOptions } from './selection-strategies/selection-strategies-types';
5
5
  /**
6
- * Pool types.
7
- *
8
- * @enum
9
- * @internal
6
+ * Enumeration of pool types.
10
7
  */
11
- export declare enum PoolType {
8
+ export declare const PoolTypes: Readonly<{
12
9
  /**
13
10
  * Fixed pool type.
14
11
  */
15
- FIXED = "fixed",
12
+ readonly fixed: "fixed";
16
13
  /**
17
14
  * Dynamic pool type.
18
15
  */
19
- DYNAMIC = "dynamic"
20
- }
16
+ readonly dynamic: "dynamic";
17
+ }>;
18
+ /**
19
+ * Pool type.
20
+ */
21
+ export type PoolType = keyof typeof PoolTypes;
21
22
  /**
22
23
  * Pool events emitter.
23
24
  */
@@ -30,11 +31,26 @@ export declare const PoolEvents: Readonly<{
30
31
  readonly full: "full";
31
32
  readonly busy: "busy";
32
33
  readonly error: "error";
34
+ readonly taskError: "taskError";
33
35
  }>;
34
36
  /**
35
37
  * Pool event.
36
38
  */
37
39
  export type PoolEvent = keyof typeof PoolEvents;
40
+ /**
41
+ * Pool information.
42
+ */
43
+ export interface PoolInfo {
44
+ type: PoolType;
45
+ minSize: number;
46
+ maxSize: number;
47
+ workerNodes: number;
48
+ idleWorkerNodes: number;
49
+ busyWorkerNodes: number;
50
+ runningTasks: number;
51
+ queuedTasks: number;
52
+ maxQueuedTasks: number;
53
+ }
38
54
  /**
39
55
  * Worker tasks queue options.
40
56
  */
@@ -114,9 +130,9 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
114
130
  */
115
131
  readonly type: PoolType;
116
132
  /**
117
- * Pool maximum size.
133
+ * Pool information.
118
134
  */
119
- readonly size: number;
135
+ readonly info: PoolInfo;
120
136
  /**
121
137
  * Pool worker nodes.
122
138
  */
@@ -128,7 +144,8 @@ export interface IPool<Worker extends IWorker, Data = unknown, Response = unknow
128
144
  *
129
145
  * - `'full'`: Emitted when the pool is dynamic and full.
130
146
  * - `'busy'`: Emitted when the pool is busy.
131
- * - `'error'`: Emitted when an error occurs.
147
+ * - `'error'`: Emitted when an uncaught error occurs.
148
+ * - `'taskError'`: Emitted when an error occurs while executing a task.
132
149
  */
133
150
  readonly emitter?: PoolEmitter;
134
151
  /**
@@ -1,7 +1,5 @@
1
- import type { PoolOptions } from '../pool';
2
- import { PoolType } from '../pool';
3
- import type { ThreadWorkerWithMessageChannel } from './fixed';
4
- import { FixedThreadPool } from './fixed';
1
+ import { type PoolOptions, type PoolType } from '../pool';
2
+ import { FixedThreadPool, type ThreadWorkerWithMessageChannel } from './fixed';
5
3
  /**
6
4
  * A thread pool with a dynamic number of threads, but a guaranteed minimum number of threads.
7
5
  *
@@ -29,7 +27,7 @@ export declare class DynamicThreadPool<Data = unknown, Response = unknown> exten
29
27
  /** @inheritDoc */
30
28
  protected get full(): boolean;
31
29
  /** @inheritDoc */
32
- get size(): number;
30
+ protected get maxSize(): number;
33
31
  /** @inheritDoc */
34
32
  protected get busy(): boolean;
35
33
  }
@@ -2,8 +2,7 @@
2
2
  import { MessageChannel, Worker } from 'node:worker_threads';
3
3
  import type { Draft, MessageValue } from '../../utility-types';
4
4
  import { AbstractPool } from '../abstract-pool';
5
- import type { PoolOptions } from '../pool';
6
- import { PoolType } from '../pool';
5
+ import { type PoolOptions, type PoolType } from '../pool';
7
6
  /**
8
7
  * A thread worker with message channels for communication between main thread and thread worker.
9
8
  */
@@ -44,7 +43,9 @@ export declare class FixedThreadPool<Data = unknown, Response = unknown> extends
44
43
  /** @inheritDoc */
45
44
  get type(): PoolType;
46
45
  /** @inheritDoc */
47
- get size(): number;
46
+ protected get minSize(): number;
47
+ /** @inheritDoc */
48
+ protected get maxSize(): number;
48
49
  /** @inheritDoc */
49
50
  protected get full(): boolean;
50
51
  /** @inheritDoc */
package/lib/queue.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare class Queue<T> {
7
7
  private items;
8
8
  private head;
9
9
  private tail;
10
+ private max;
10
11
  constructor();
11
12
  /**
12
13
  * Get the size of the queue.
@@ -15,6 +16,13 @@ export declare class Queue<T> {
15
16
  * @readonly
16
17
  */
17
18
  get size(): number;
19
+ /**
20
+ * Get the maximum size of the queue.
21
+ *
22
+ * @returns The maximum size of the queue.
23
+ * @readonly
24
+ */
25
+ get maxSize(): number;
18
26
  /**
19
27
  * Enqueue an item.
20
28
  *
@@ -25,9 +25,13 @@ export interface MessageValue<Data = unknown, MainWorker extends ClusterWorker |
25
25
  */
26
26
  readonly kill?: KillBehavior | 1;
27
27
  /**
28
- * Error.
28
+ * Task error.
29
29
  */
30
30
  readonly error?: string;
31
+ /**
32
+ * Task data triggering task error.
33
+ */
34
+ readonly errorData?: unknown;
31
35
  /**
32
36
  * Runtime.
33
37
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poolifier",
3
- "version": "2.5.1",
3
+ "version": "2.5.2",
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",
@@ -91,7 +91,7 @@
91
91
  "c8": "^7.14.0",
92
92
  "eslint": "^8.41.0",
93
93
  "eslint-config-standard": "^17.1.0",
94
- "eslint-config-standard-with-typescript": "^34.0.1",
94
+ "eslint-config-standard-with-typescript": "^35.0.0",
95
95
  "eslint-define-config": "^1.20.0",
96
96
  "eslint-import-resolver-typescript": "^3.5.5",
97
97
  "eslint-plugin-import": "^2.27.5",
@@ -107,7 +107,7 @@
107
107
  "mocha": "^10.2.0",
108
108
  "mochawesome": "^7.1.3",
109
109
  "prettier": "^2.8.8",
110
- "release-it": "^15.10.3",
110
+ "release-it": "^15.10.5",
111
111
  "rollup": "^3.23.0",
112
112
  "rollup-plugin-analyzer": "^4.0.0",
113
113
  "rollup-plugin-command": "^1.1.3",